File manager - Edit - /home/jardides/www/Jardi-design/images/administrator/dav.tar
Back
templates/exception.php 0000604 00000002217 15247100403 0011243 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ style('core', ['styles', 'header']); /** @param $_ array */ ?> <span class="error error-wide"> <h2><strong><?php p($_['title']) ?></strong></h2> <br> <h2><strong><?php p($l->t('Technical details')) ?></strong></h2> <ul> <li><?php p($l->t('Remote Address: %s', $_['remoteAddr'])) ?></li> <li><?php p($l->t('Request ID: %s', $_['requestID'])) ?></li> </ul> </span> l10n/id.json 0000604 00000000453 15247100403 0006577 0 ustar 00 { "translations": { "Contact birthdays" : "Ulang tahun kontak", "Personal" : "Pribadi", "Contacts" : "Kontak", "Technical details" : "Rincian teknis", "Remote Address: %s" : "Alamat remote: %s", "Request ID: %s" : "ID Permintaan: %s" },"pluralForm" :"nplurals=1; plural=0;" } l10n/bg.js 0000604 00000011602 15247100403 0006234 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Календар", "Todos" : "Задачи", "{actor} created calendar {calendar}" : "{actor} направи календар {calendar}", "You created calendar {calendar}" : "Направихте календар {calendar}", "{actor} deleted calendar {calendar}" : "{actor} изтри календар {calendar}", "You deleted calendar {calendar}" : "Изтрихте календар {calendar}", "{actor} updated calendar {calendar}" : "{actor} обнови календар {calendar}", "You updated calendar {calendar}" : "Обновихте календар {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} сподели календар {calendar} с теб", "You shared calendar {calendar} with {user}" : "Споделихте календар {calendar} с {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} сподели календар {calendar} с {user}", "{actor} unshared calendar {calendar} from you" : "{actor} отказа споделяне на календар {calendar} с теб", "You unshared calendar {calendar} from {user}" : "Отказахте споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} отказа споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} отказа споделяне на календар {calendar} от себеси", "You shared calendar {calendar} with group {group}" : "Споделихте календар {calendar} с група {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} сподели календар {calendar} с група {group}", "You unshared calendar {calendar} from group {group}" : "Отказахте споделяне на календар {calendar} от група {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} отказа споделяне с календар {calendar} от група {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} създаде събитие {event} в календар {calendar}", "You created event {event} in calendar {calendar}" : "Създадохте събитие {event} в календар {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} изтри събитие {event} от календар {calendar}", "You deleted event {event} from calendar {calendar}" : "Изтрихте събитие {event} от календар {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} обнови събитие {event} в календар {calendar}", "You updated event {event} in calendar {calendar}" : "Обновихте събитие {event} в календар {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} създаде задача {todo} в списък {calendar}", "You created todo {todo} in list {calendar}" : "Създадохте задача {todo} в списък {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} изтри задача {todo} от списък {calendar}", "You deleted todo {todo} from list {calendar}" : "Изтрихте задача {todo} от лист {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} актуализира задача {todo} в списък {calendar}", "You updated todo {todo} in list {calendar}" : "Променихте задача {todo} в списък {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} реши задача {todo} в списък {calendar}", "You solved todo {todo} in list {calendar}" : "Решихте задача {todo} в списък {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} възобнови задача {todo} в списък {calendar}", "You reopened todo {todo} in list {calendar}" : "Възобновихте задача {todo} в списък {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Календар</strong> беше променен", "A calendar <strong>event</strong> was modified" : "Календарно <strong>събитие</strong> беше променено", "A calendar <strong>todo</strong> was modified" : "Календарна <strong>задача</strong> беше променена", "Contact birthdays" : "Рождени дни на контакти", "Personal" : "Личен", "Contacts" : "Контакти", "Technical details" : "Технически детайли", "Remote Address: %s" : "Отдалечен адрес: %s", "Request ID: %s" : "ID на заявка: %s" }, "nplurals=2; plural=(n != 1);"); l10n/zh_TW.json 0000604 00000000450 15247100403 0007233 0 ustar 00 { "translations": { "Contact birthdays" : "聯絡人生日", "Personal" : "個人", "Contacts" : "聯絡人", "Technical details" : "技術細節", "Remote Address: %s" : "遠端位置:%s", "Request ID: %s" : "請求編號:%s" },"pluralForm" :"nplurals=1; plural=0;" } l10n/bg_BG.js 0000604 00000011602 15247100403 0006604 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Календар", "Todos" : "Задачи", "{actor} created calendar {calendar}" : "{actor} направи календар {calendar}", "You created calendar {calendar}" : "Направихте календар {calendar}", "{actor} deleted calendar {calendar}" : "{actor} изтри календар {calendar}", "You deleted calendar {calendar}" : "Изтрихте календар {calendar}", "{actor} updated calendar {calendar}" : "{actor} обнови календар {calendar}", "You updated calendar {calendar}" : "Обновихте календар {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} сподели календар {calendar} с теб", "You shared calendar {calendar} with {user}" : "Споделихте календар {calendar} с {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} сподели календар {calendar} с {user}", "{actor} unshared calendar {calendar} from you" : "{actor} отказа споделяне на календар {calendar} с теб", "You unshared calendar {calendar} from {user}" : "Отказахте споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} отказа споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} отказа споделяне на календар {calendar} от себеси", "You shared calendar {calendar} with group {group}" : "Споделихте календар {calendar} с група {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} сподели календар {calendar} с група {group}", "You unshared calendar {calendar} from group {group}" : "Отказахте споделяне на календар {calendar} от група {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} отказа споделяне с календар {calendar} от група {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} създаде събитие {event} в календар {calendar}", "You created event {event} in calendar {calendar}" : "Създадохте събитие {event} в календар {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} изтри събитие {event} от календар {calendar}", "You deleted event {event} from calendar {calendar}" : "Изтрихте събитие {event} от календар {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} обнови събитие {event} в календар {calendar}", "You updated event {event} in calendar {calendar}" : "Обновихте събитие {event} в календар {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} създаде задача {todo} в списък {calendar}", "You created todo {todo} in list {calendar}" : "Създадохте задача {todo} в списък {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} изтри задача {todo} от списък {calendar}", "You deleted todo {todo} from list {calendar}" : "Изтрихте задача {todo} от лист {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} актуализира задача {todo} в списък {calendar}", "You updated todo {todo} in list {calendar}" : "Променихте задача {todo} в списък {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} реши задача {todo} в списък {calendar}", "You solved todo {todo} in list {calendar}" : "Решихте задача {todo} в списък {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} възобнови задача {todo} в списък {calendar}", "You reopened todo {todo} in list {calendar}" : "Възобновихте задача {todo} в списък {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Календар</strong> беше променен", "A calendar <strong>event</strong> was modified" : "Календарно <strong>събитие</strong> беше променено", "A calendar <strong>todo</strong> was modified" : "Календарна <strong>задача</strong> беше променена", "Contact birthdays" : "Рождени дни на контакти", "Personal" : "Личен", "Contacts" : "Контакти", "Technical details" : "Технически детайли", "Remote Address: %s" : "Отдалечен адрес: %s", "Request ID: %s" : "ID на заявка: %s" }, "nplurals=2; plural=(n != 1);"); l10n/es_MX.js 0000604 00000010467 15247100403 0006667 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendario", "Todos" : "Pendientes", "{actor} created calendar {calendar}" : "{actor} creó el calendario {calendar}", "You created calendar {calendar}" : "Creaste el calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} borró el calendario {calendar}", "You deleted calendar {calendar}" : "Borraste el calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizó el calendario {calendar}", "You updated calendar {calendar}" : "Actualizaste el calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} ha compartido el calendario {calendar} contigo", "You shared calendar {calendar} with {user}" : "Compartiste el calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartió el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ha dejado de compartir el calendario {calendar} contigo", "You unshared calendar {calendar} from {user}" : "Has dejado de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} dejó de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} dejó de compartir {el calendario calendar} con él mismo", "You shared calendar {calendar} with group {group}" : "Compartiste el calendario {calendar} con el grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartió el calendario {calendar} con el grupo {group}", "You unshared calendar {calendar} from group {group}" : "Dejaste de compartir el calendario {calendar} con el grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} dejó de compartir el calendrio {calendar} con el grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creó el evento {event} en el calendario {calendar}", "You created event {event} in calendar {calendar}" : "Creaste el evento {event} en el calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} borró el eventó {event} del calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Borraste el evento {event} del calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizó el evento {event} en el calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Actualizaste el evento {event} en el calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creó el pendiente {todo} en la lista {calendar}", "You created todo {todo} in list {calendar}" : "Creaste el pendiente {todo} en la lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} borró el pendiente {todo} de la lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Borraste el pendiente {todo} de la lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizó el pendiente {todo} de la lista {calendar}", "You updated todo {todo} in list {calendar}" : "Actualizaste el pendiente {todo} de la lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} resolvió el pendiente {todo} de la lista {calendar}", "You solved todo {todo} in list {calendar}" : "Resolviste el pendiente {todo} de la lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabrió el pendiente {todo} de la lista{calendar}", "You reopened todo {todo} in list {calendar}" : "Reabriste el pendiente {todo} de la lista {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> fue modificado", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> de un calendario fue modificado", "A calendar <strong>todo</strong> was modified" : "Un <strong>pendiente</strong> de un calendario fue modificado", "Contact birthdays" : "Cumpleaños del contacto", "Personal" : "Personal", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s" }, "nplurals=2; plural=(n != 1);"); l10n/pl.json 0000604 00000011040 15247100403 0006610 0 ustar 00 { "translations": { "Calendar" : "Kalendarz", "Todos" : "Zadania", "{actor} created calendar {calendar}" : "{actor} utworzył/-a kalendarz {calendar}", "You created calendar {calendar}" : "Utworzyłeś/-aś kalendarz {calendar}", "{actor} deleted calendar {calendar}" : "{actor} usunął/-ęła kalendarz {calendar} .", "You deleted calendar {calendar}" : "Usunąłęś/ęłaś kalendarz {calendar}", "{actor} updated calendar {calendar}" : "{actor} zaktualizował/-a kalendarz {calendar} .", "You updated calendar {calendar}" : "Zaktualizowałeś/-aś kalendarz {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} współdzieli kalendarz {calendar} z Tobą", "You shared calendar {calendar} with {user}" : "Współdzielisz kalendarz {calendar} z {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} współdzieli kalendarz {calendar} z {user}", "{actor} unshared calendar {calendar} from you" : "{actor} zakończył/-a współdzielienie kalendarza {calendar} z Tobą", "You unshared calendar {calendar} from {user}" : "Zakończyłeś/-aś współdzielenie kalendarza {calendar} z {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} zakończył/-a współdzielenie kalendarza {calendar} z {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} zakończył/-a wspóldzielenie kalendarza {calendar} od siebie", "You shared calendar {calendar} with group {group}" : "Współdzielisz kalendarz {calendar} z grupą {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} współdzieli kalendarz {calendar} z grupą {group}", "You unshared calendar {calendar} from group {group}" : "Zakończyłeś współdzielenie kalendarza {calendar} z grupą {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} nie współdzieli kalendarza {calendar} z grupą {group} .", "{actor} created event {event} in calendar {calendar}" : "{actor} utworzył/-a wydarzenie {event} w kalendarzu {calendar}", "You created event {event} in calendar {calendar}" : "Utworzyłeś/-ał wydarzenie {event} w kalendarzu {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} usunął/-ęłą wydarzenie {event} z kalendarza {calendar}", "You deleted event {event} from calendar {calendar}" : "Usunąłeś/-aś wydarzenie {event} z kalendarza {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} zaktualizował/-a wydarzenie {event} z kalendarza {calendar}", "You updated event {event} in calendar {calendar}" : "Zaktualizowałeś/-aś wydarzenie {event} w kalendarzu {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} utworzył/-a zadanie {todo} na liście {calendar}", "You created todo {todo} in list {calendar}" : "Utworzyłeś/-aś zadanie {todo} na liście {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} usunął/-ęła zadanie {todo} z listy {calendar}", "You deleted todo {todo} from list {calendar}" : "Usunąłeś/-ęłaś zadanie {todo} z listy {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} zaktualizował/-a zadanie {todo} na liście {calendar}", "You updated todo {todo} in list {calendar}" : "Zaktualizowałeś/-aś zadanie {todo} na liście {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} zakończył zadanie {todo} na liście {calendar}", "You solved todo {todo} in list {calendar}" : "Zakończyłeś/-aś zadanie {todo} na liście {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} otworzył ponownie zadanie {todo} na liście {calendar}", "You reopened todo {todo} in list {calendar}" : "Otworzyłęś/-aś ponownie zadanie {todo} na liście {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalendarz</strong> został zmodyfikowany", "A calendar <strong>event</strong> was modified" : "<strong>Zdarzenie</strong> kalendarza zostało zmodyfikowane", "A calendar <strong>todo</strong> was modified" : "Kalendarz <strong>zadań</strong> został zmieniony", "Contact birthdays" : "Urodziny kontaktu", "Personal" : "Osobiste", "Contacts" : "Kontakty", "Technical details" : "Szczegóły techniczne", "Remote Address: %s" : "Adres zdalny: %s", "Request ID: %s" : "ID żądania: %s" },"pluralForm" :"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);" } l10n/ja.js 0000604 00000010350 15247100403 0006235 0 ustar 00 OC.L10N.register( "dav", { "A <strong>calendar</strong> was modified" : "<strong>カレンダー</strong>が変更されました", "A calendar <strong>event</strong> was modified" : "カレンダーの<strong>イベント</strong>が変更されました", "A calendar <strong>todo</strong> was modified" : "カレンダーの<strong>ToDo</strong>が変更されました", "%1$s created calendar %2$s" : "%1$s はカレンダー %2$s を作成しました", "You created calendar %2$s" : "カレンダー %2$s を作成しました", "%1$s deleted calendar %2$s" : "%1$s はカレンダー %2$s を削除しました", "You deleted calendar %2$s" : "カレンダー %2$s を削除しました", "%1$s updated calendar %2$s" : "%1$s はカレンダー %2$s を更新しました", "You updated calendar %2$s" : "カレンダー %2$s を更新しました", "%1$s shared calendar %2$s with you" : "%1$s がカレンダー %2$s を共有しています", "You shared calendar %2$s with %1$s" : "%1$s とカレンダー %2$s を共有しました", "%3$s shared calendar %2$s with %1$s" : "%3$s が %1$s とカレンダー %2$s を共有しています", "%1$s unshared calendar %2$s from you" : "%1$s があなたからカレンダー %2$s を共有解除しました", "You unshared calendar %2$s from %1$s" : "%1$s からカレンダー %2$s を共有解除しました", "%3$s unshared calendar %2$s from %1$s" : "%3$s が %1$s からカレンダー %2$s を共有解除しました", "%1$s unshared calendar %2$s from themselves" : "%1$s が彼らからカレンダー %2$s を共有解除しました", "You shared calendar %2$s with group %1$s" : "カレンダー %2$s をグループ %1$s と共有しました", "%3$s shared calendar %2$s with group %1$s" : "%3$s が %1$s グループとカレンダー %2$s を共有しました", "You unshared calendar %2$s from group %1$s" : "%1$s グループからカレンダー %2$s を共有解除しました", "%3$s unshared calendar %2$s from group %1$s" : "%3$s が %1$s グループからカレンダー %2$s を共有解除しました", "%1$s created event %3$s in calendar %2$s" : "%1$s がカレンダー %2$s に %3$s イベントを作成", "You created event %3$s in calendar %2$s" : "カレンダー %2$s に %3$s イベントを作成しました", "%1$s deleted event %3$s from calendar %2$s" : "%1$s が カレンダー %2$s から %3$s イベントを削除しました ", "You deleted event %3$s from calendar %2$s" : "カレンダー %2$s から %3$s イベントを削除しました", "%1$s updated event %3$s in calendar %2$s" : "%1$s がカレンダー %2$s の %3$s イベントを更新しました", "You updated event %3$s in calendar %2$s" : "カレンダー %2$s で %3$s イベントを更新しました", "%1$s created todo %3$s in list %2$s" : "%1$s がリスト %2$s にTodo %3$s を作成しました", "You created todo %3$s in list %2$s" : "リスト %2$s に タスク %3$s を作成しました", "%1$s deleted todo %3$s from list %2$s" : " %1$s が リスト%2$s からタスク %3$s を 削除しました", "You deleted todo %3$s from list %2$s" : "リスト %2$s から タスク %3$s を削除しました", "%1$s updated todo %3$s in list %2$s" : "%1$s が リスト%2$s のタスク %3$s を更新しました", "You updated todo %3$s in list %2$s" : "リスト %2$s のタスク %3$s を更新しました", "%1$s solved todo %3$s in list %2$s" : "%1$s が リスト%2$s の タスク %3$s を完了しました", "You solved todo %3$s in list %2$s" : "リスト %2$s のタスク %3$s を完了しました", "%1$s reopened todo %3$s in list %2$s" : "%1$s はリスト %2$s のタスク %3$s を再オープンしました", "You reopened todo %3$s in list %2$s" : "リスト%2$s のタスク %3$s を再オープンしました", "Calendar" : "カレンダー", "Todos" : "ToDo", "Contact birthdays" : "誕生日", "Personal" : "個人", "Contacts" : "アドレス帳", "Technical details" : "技術的な詳細", "Remote Address: %s" : "リモートアドレス: %s", "Request ID: %s" : "リクエスト ID: %s" }, "nplurals=1; plural=0;"); l10n/ja.json 0000604 00000010345 15247100403 0006576 0 ustar 00 { "translations": { "A <strong>calendar</strong> was modified" : "<strong>カレンダー</strong>が変更されました", "A calendar <strong>event</strong> was modified" : "カレンダーの<strong>イベント</strong>が変更されました", "A calendar <strong>todo</strong> was modified" : "カレンダーの<strong>ToDo</strong>が変更されました", "%1$s created calendar %2$s" : "%1$s はカレンダー %2$s を作成しました", "You created calendar %2$s" : "カレンダー %2$s を作成しました", "%1$s deleted calendar %2$s" : "%1$s はカレンダー %2$s を削除しました", "You deleted calendar %2$s" : "カレンダー %2$s を削除しました", "%1$s updated calendar %2$s" : "%1$s はカレンダー %2$s を更新しました", "You updated calendar %2$s" : "カレンダー %2$s を更新しました", "%1$s shared calendar %2$s with you" : "%1$s がカレンダー %2$s を共有しています", "You shared calendar %2$s with %1$s" : "%1$s とカレンダー %2$s を共有しました", "%3$s shared calendar %2$s with %1$s" : "%3$s が %1$s とカレンダー %2$s を共有しています", "%1$s unshared calendar %2$s from you" : "%1$s があなたからカレンダー %2$s を共有解除しました", "You unshared calendar %2$s from %1$s" : "%1$s からカレンダー %2$s を共有解除しました", "%3$s unshared calendar %2$s from %1$s" : "%3$s が %1$s からカレンダー %2$s を共有解除しました", "%1$s unshared calendar %2$s from themselves" : "%1$s が彼らからカレンダー %2$s を共有解除しました", "You shared calendar %2$s with group %1$s" : "カレンダー %2$s をグループ %1$s と共有しました", "%3$s shared calendar %2$s with group %1$s" : "%3$s が %1$s グループとカレンダー %2$s を共有しました", "You unshared calendar %2$s from group %1$s" : "%1$s グループからカレンダー %2$s を共有解除しました", "%3$s unshared calendar %2$s from group %1$s" : "%3$s が %1$s グループからカレンダー %2$s を共有解除しました", "%1$s created event %3$s in calendar %2$s" : "%1$s がカレンダー %2$s に %3$s イベントを作成", "You created event %3$s in calendar %2$s" : "カレンダー %2$s に %3$s イベントを作成しました", "%1$s deleted event %3$s from calendar %2$s" : "%1$s が カレンダー %2$s から %3$s イベントを削除しました ", "You deleted event %3$s from calendar %2$s" : "カレンダー %2$s から %3$s イベントを削除しました", "%1$s updated event %3$s in calendar %2$s" : "%1$s がカレンダー %2$s の %3$s イベントを更新しました", "You updated event %3$s in calendar %2$s" : "カレンダー %2$s で %3$s イベントを更新しました", "%1$s created todo %3$s in list %2$s" : "%1$s がリスト %2$s にTodo %3$s を作成しました", "You created todo %3$s in list %2$s" : "リスト %2$s に タスク %3$s を作成しました", "%1$s deleted todo %3$s from list %2$s" : " %1$s が リスト%2$s からタスク %3$s を 削除しました", "You deleted todo %3$s from list %2$s" : "リスト %2$s から タスク %3$s を削除しました", "%1$s updated todo %3$s in list %2$s" : "%1$s が リスト%2$s のタスク %3$s を更新しました", "You updated todo %3$s in list %2$s" : "リスト %2$s のタスク %3$s を更新しました", "%1$s solved todo %3$s in list %2$s" : "%1$s が リスト%2$s の タスク %3$s を完了しました", "You solved todo %3$s in list %2$s" : "リスト %2$s のタスク %3$s を完了しました", "%1$s reopened todo %3$s in list %2$s" : "%1$s はリスト %2$s のタスク %3$s を再オープンしました", "You reopened todo %3$s in list %2$s" : "リスト%2$s のタスク %3$s を再オープンしました", "Calendar" : "カレンダー", "Todos" : "ToDo", "Contact birthdays" : "誕生日", "Personal" : "個人", "Contacts" : "アドレス帳", "Technical details" : "技術的な詳細", "Remote Address: %s" : "リモートアドレス: %s", "Request ID: %s" : "リクエスト ID: %s" },"pluralForm" :"nplurals=1; plural=0;" } l10n/hu.json 0000604 00000011115 15247100403 0006614 0 ustar 00 { "translations": { "Calendar" : "Naptár", "Todos" : "Teendők", "{actor} created calendar {calendar}" : "{actor} létrehozta a naptárt: {calendar}", "You created calendar {calendar}" : "Létrehoztad a naptárt: {calendar}", "{actor} deleted calendar {calendar}" : "{actor} törölte a naptárt: {calendar}", "You deleted calendar {calendar}" : "Törölted a naptárt: {calendar}", "{actor} updated calendar {calendar}" : "{actor} frissítette a napárt: {calendar}", "You updated calendar {calendar}" : "Frissítetted a naptárt: {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} megosztotta veled ezt a naptárt: {calendar}", "You shared calendar {calendar} with {user}" : "Megosztottad ezt a napárt: {calendar} vele: {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} megosztotta ezt a napárt: {calendar} vele: {user}", "{actor} unshared calendar {calendar} from you" : "{actor} visszavonta töled a naptár megosztását: {calendar}", "You unshared calendar {calendar} from {user}" : "Visszavontad a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} visszavonta a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} visszavonta tőlük a naptár megosztását: {calendar}", "You shared calendar {calendar} with group {group}" : "Megosztottad ezt a naptárt: {calendar} evvel a csoporttal: {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} megosztotta ezt a naptárt: {calendar} evvel a csoporttal: {group}", "You unshared calendar {calendar} from group {group}" : "Visszavontad ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} visszavonta ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} létrehozta ezt az eseményt: {event} ebben a naptárban: {calendar}", "You created event {event} in calendar {calendar}" : "Létrehoztad ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} törölte ezt az eseményt: {event} ebből a naptárból: {calendar}", "You deleted event {event} from calendar {calendar}" : "Törölted ezt az eseményt: {event} ebből a naptárból: {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} frissítette ezt az eseményt: {event} ebben a naptárban: {calendar}", "You updated event {event} in calendar {calendar}" : "Frissítetted ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} létrehozta ezt a teendőt: {todo} ebben a listában: {calendar}", "You created todo {todo} in list {calendar}" : "Létrehoztad ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} törölte ezt a teendőt: {todo} ebből a listából: {calendar}", "You deleted todo {todo} from list {calendar}" : "Törölted ezt a teendőt: {todo} ebből a listából: {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} frissítette ezt a teendőt: {todo} ebben a listában: {calendar}", "You updated todo {todo} in list {calendar}" : "Frissítetted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} elintézte ezt a teendőt: {todo} ebben a listában: {calendar}", "You solved todo {todo} in list {calendar}" : "Elintézted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} újranyitotta ezt a teendőt: {todo} ebben a listában: {calendar}", "You reopened todo {todo} in list {calendar}" : "Újranyitottad ezt a teendőt: {todo} ebben a listában: {calendar}", "A <strong>calendar</strong> was modified" : "Egy <strong>naptár</strong> megváltozott", "A calendar <strong>event</strong> was modified" : "Egy naptár <strong>esemény</strong> megváltozott", "A calendar <strong>todo</strong> was modified" : "Egy naptár <strong>teendő</strong> megváltozott", "Contact birthdays" : "Születésnapok", "Personal" : "Személyes", "Contacts" : "Névjegyek", "Technical details" : "Technikai adatok", "Remote Address: %s" : "Távoli cím: %s", "Request ID: %s" : "Kérelem azonosító: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/da.js 0000604 00000010206 15247100403 0006227 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Opgaver", "{actor} created calendar {calendar}" : "{actor} oprettede kalenderen {calendar}", "You created calendar {calendar}" : "Du oprettede kalenderen {calendar}", "{actor} deleted calendar {calendar}" : "{actor} sletede kalenderen {calendar}", "You deleted calendar {calendar}" : "Du slettede kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} opdaterede kalenderen {calendar}", "You updated calendar {calendar}" : "Du opdaterede kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med dig", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} fjernede delingen af {calendar} fra dig", "You unshared calendar {calendar} from {user}" : "Du fjernede delingen af {calendar} fra {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} fjernede delingen af {calendar} fra {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} fjernede delingen af {calendar} fra sig selv", "You shared calendar {calendar} with group {group}" : "Du delte kalenderen {calendar} med gruppen {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delte kalenderen {calendar} med gruppen {group}", "You unshared calendar {calendar} from group {group}" : "Du fjernede delingen af {calendar} fra gruppen {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} fjernede deling af kalenderen {calendar} fra gruppen {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} oprettede begivenheden {event} i kalenderen {calendar}", "You created event {event} in calendar {calendar}" : "Du oprettede begivenheden {event} i kalenderen {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} slettede begivenheden {event} fra kalenderen {calendar}", "You deleted event {event} from calendar {calendar}" : "Du slettede begivenheden {event} fra kalenderen {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} opdaterede begivenheden {event} i kalenderen {calendar}", "You updated event {event} in calendar {calendar}" : "Du opdaterede begivenheden {event} i kalenderen {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} oprettede en opgave {todo} i listen {calendar}", "You created todo {todo} in list {calendar}" : "Du oprettede opgaven {todo} i listen {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} slettede opgaven {todo} fra listen {calendar}", "You deleted todo {todo} from list {calendar}" : "Du slettede opgaven {todo} fra listen {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} opdaterede opgaven {todo} i listen {calendar}", "You updated todo {todo} in list {calendar}" : "Du opdaterede opgaven {todo} i listen {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} løste opgaven {todo} i listen {calendar}", "You solved todo {todo} in list {calendar}" : "Du løste opgaven {todo} i listen {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} genåbnede opgaven {todo} i listen {calendar}", "You reopened todo {todo} in list {calendar}" : "Du genåbnede opgaven {todo} i listen {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> er blevet ændret", "A calendar <strong>event</strong> was modified" : "En kalender <strong>begivenhed</strong> er blevet ændret", "A calendar <strong>todo</strong> was modified" : "En kalender <strong>opgave</strong> blev ændret", "Contact birthdays" : "Kontakt fødselsdag", "Personal" : "Personligt", "Contacts" : "Kontakter", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Fjernadresse: %s", "Request ID: %s" : "Forespørgsels-ID: %s" }, "nplurals=2; plural=(n != 1);"); l10n/el.json 0000604 00000013447 15247100403 0006612 0 ustar 00 { "translations": { "Calendar" : "Ημερολόγιο", "Todos" : "Εργασίες προς εκτέλεση", "{actor} created calendar {calendar}" : "{actor} δημιουργήθηκε το ημερολόγιο {calendar}", "You created calendar {calendar}" : "Δημιουργήσατε ημερολόγιο {ημερολόγιο}", "{actor} deleted calendar {calendar}" : "{actor} διέγραψε το ημερολόγιο {calendar}", "You deleted calendar {calendar}" : "Διαγράψατε το ημερολόγιο {calendar}", "{actor} updated calendar {calendar}" : "{actor} ενημέρωσε το ημερολόγιο {calendar}", "You updated calendar {calendar}" : "Έχετε ενημερώσει το ημερολόγιο {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} διαμοιράστηκε το ημερολόγιο {calendar} με εσάς", "You shared calendar {calendar} with {user}" : "Διαμοιραστήκατε το ημερολογίου {calendar} με {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} διαμοίρασε το ημερολόγιο {calendar} με {user}", "{actor} unshared calendar {calendar} from you" : "Ο {actor} σταμάτησε τον διαμοιρασμό του ημερολογίου {calendar} από εσάς", "You unshared calendar {calendar} from {user}" : "Σταματήσατε τον διαμοιρασμό ημερολογίου {calendar} από {user}", "{actor} unshared calendar {calendar} from {user}" : "Ο {actor} σταμάτησε τον διαμοιρασμό του ημερολογίου {calendar} από τον χρήστη {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} σταμάτησε το διαμοιρασμένο ημερολόγιο {calendar} από τον εαυτό τους", "You shared calendar {calendar} with group {group}" : "Διαμοιραστείκατε ένα ημερολόγιο {calendar} με την ομάδα {group}", "{actor} shared calendar {calendar} with group {group}" : "Ο {actor} διαμοιράστηκε το ημερολόγιο {calendar} με την ομάδα {group}", "You unshared calendar {calendar} from group {group}" : "Σταματήσατε τον διαμοιρασμό του ημερολογίου {calendar} από την ομάδα {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} σταμάτησε το διαμοιρασμένο ημερολόγιο {calendar} από την ομάδα {group}", "{actor} created event {event} in calendar {calendar}" : "Ο {actor} δημιούργησε το γεγονός {event} στο ημερολόγιο {calendar}", "You created event {event} in calendar {calendar}" : "Δημιουργήσατε το γεγονός {event} στο ημερολόγιο {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "Ο {actor} διέγραψε το γεγονός {event} από το ημερολόγιο {calendar}", "You deleted event {event} from calendar {calendar}" : "Διαγράψατε το συμβάν {event} από το ημερολόγιο {calendar}", "{actor} updated event {event} in calendar {calendar}" : "Ο {actor} ενημέρωσε το γεγονός {event} στο ημερολόγιο {calendar}", "You updated event {event} in calendar {calendar}" : "Ενημερώσατε το συμβάν {event} στο ημερολόγιο {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} δημιούργησε την εκκρεμότητα {todo} στη λίστα {ημερολόγιο}", "You created todo {todo} in list {calendar}" : "Δημιουργήσατε την εκκρεμότητα {todo} στη λίστα {ημερολόγιο}", "{actor} deleted todo {todo} from list {calendar}" : "Ο {actor} διέγραψε την εκκρεμότητα {todo} από τη λίστα {ημερολόγιο}", "You deleted todo {todo} from list {calendar}" : "Διέγραψες την εκκρεμότητα {todo} από τη λίστα {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} ενημέρωσε την εκκρεμότητα {todo} στη λίστα {calendar}", "You updated todo {todo} in list {calendar}" : "Ενημέρωσες την εκκρεμότητα {todo} στη λίστα {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} επίλυσε την εκκρεμότητα {todo} στην λίστα {calendar}", "You solved todo {todo} in list {calendar}" : "Επίλυσες την εκκρεμότητα {todo} στην λίστα {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} άνοιξε ξανά την εκκρεμότητα {todo} στην λίστα {calendar}", "You reopened todo {todo} in list {calendar}" : "Άνοιξες ξανά την εκκρεμότητα {todo} στην λίστα {calendar}", "A <strong>calendar</strong> was modified" : "Τροποποιήθηκε ένα <strong>ημερολόγιο</strong> ", "A calendar <strong>event</strong> was modified" : "Τροποποιήθηκε ένα <strong>γεγονός</strong> του ημερολογίου", "A calendar <strong>todo</strong> was modified" : "Ενός ημερολογίου η <strong>εκκρεμότητα</strong> τροποποιήθηκε", "Contact birthdays" : "Γενέθλια επαφών", "Personal" : "Προσωπικά", "Contacts" : "Επαφές", "Technical details" : "Τεχνικές λεπτομέρειες", "Remote Address: %s" : "Απομακρυσμένη Διεύθυνση: %s", "Request ID: %s" : "ID Αιτήματος: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/cs.json 0000604 00000010515 15247100403 0006610 0 ustar 00 { "translations": { "Calendar" : "Kalendář", "Todos" : "Úkoly", "{actor} created calendar {calendar}" : "{actor} vytvořil(a) kalendář {calendar}", "You created calendar {calendar}" : "Vytvořil(a", "{actor} deleted calendar {calendar}" : "{actor} smazal(a) kalendář {calendar}", "You deleted calendar {calendar}" : "Smazal(a) jste kalendář {calendar}", "{actor} updated calendar {calendar}" : "{actor} aktualizoval(a) kalendář {calendar}", "You updated calendar {calendar}" : "Aktualizoval(a) jste kalendář {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} s vámi nasdílel(a) kalendář {calendar}", "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} začal sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from you" : "{actor} s vámi přestal(a) sdílet kalendář {calendar}", "You unshared calendar {calendar} from {user}" : "S uživatelem {user} jste přestal(a) sdílet kalendář {calendar}", "{actor} unshared calendar {calendar} from {user}" : "{actor} přestal(a) sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} přestal sdílet kalendář {calendar} sám se sebou", "You shared calendar {calendar} with group {group}" : "Se skupinou {group} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with group {group}" : "{actor} nasdílel(a) kalendář {calendar} skupině {group}", "You unshared calendar {calendar} from group {group}" : "Zrušil(a) jste sdílení kalendáře {calendar} skupině {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} přestal(a) sdílet kalendář {calendar} se skupinou {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} vytvořil(a) událost {event} v kalendáři {calendar}", "You created event {event} in calendar {calendar}" : "V kalendáři {calendar} jste vytvořil(a) událost {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} z kalendáře {calendar} smazal(a) událost {event}", "You deleted event {event} from calendar {calendar}" : "Smazal(a) jste událost {event} z kalendáře {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} aktualizoval(a) událost {event} v kalendáři {calendar}", "You updated event {event} in calendar {calendar}" : "Aktualizoval(a) jste událost {event} v kalendáři {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} vytvořil(a) v seznamu {calendar} vytvořila úkol {todo}", "You created todo {todo} in list {calendar}" : "V seznamu {calendar} jste vytvořil(a) úkol {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} smazal(a) úkol {todo} ze seznamu {calendar}", "You deleted todo {todo} from list {calendar}" : "Ze seznamu {todo} jste smazal(a) úkol {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} aktualizoval(a) úkol {todo} v seznamu {calendar}", "You updated todo {todo} in list {calendar}" : "Aktualizoval(a) jste úkol {todo} v seznamu {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} vyřešil(a) úkol {todo} v seznamu {calendar}", "You solved todo {todo} in list {calendar}" : "Vyřešil(a) jste úkol {todo} v seznamu {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} znovu otevřel(a) úkol {todo} v seznamu {calendar}", "You reopened todo {todo} in list {calendar}" : "Znovu jste otevřel(a) úkol {todo} v seznamu {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalendář</strong> byl změněn", "A calendar <strong>event</strong> was modified" : "<strong>Událost</strong> v kalendáři byla změněna", "A calendar <strong>todo</strong> was modified" : "<strong>Úkol</strong> v kalendáři byl změněn", "Contact birthdays" : "Narozeniny kontaktů", "Personal" : "Osobní", "Contacts" : "Kontakty", "Technical details" : "Technické detaily", "Remote Address: %s" : "Vzdálená adresa: %s", "Request ID: %s" : "ID požadavku: %s" },"pluralForm" :"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" } l10n/nb_NO.js 0000604 00000010247 15247100403 0006643 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Gjøremål", "{actor} created calendar {calendar}" : "{actor} opprettet kalenderen {calendar}", "You created calendar {calendar}" : "Du opprettet kalenderen {calendar}", "{actor} deleted calendar {calendar}" : "{actor} slettet kalenderen {calendar}", "You deleted calendar {calendar}" : "Du slettet kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} oppdaterte kalenderen {calendar}", "You updated calendar {calendar}" : "Du oppdaterte kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med deg", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} fjernet delingen av kalenderen {calendar} med deg", "You unshared calendar {calendar} from {user}" : "Du fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} fjernet delingen av kalender {calendar} med seg selv", "You shared calendar {calendar} with group {group}" : "Du delte kalender {calendar} med gruppe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delte kalenderen {calendar} med gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Du fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} opprettet en hendelse {event} i kalenderen {calendar}", "You created event {event} in calendar {calendar}" : "Du opprettet en hendelse {event} i kalenderen {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} slettet hendelsen {event} fra kalenderen {calendar}", "You deleted event {event} from calendar {calendar}" : "Du slettet hendelsen {event} fra kalenderen {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} oppdaterte hendelse {event} i kalenderen {calendar}", "You updated event {event} in calendar {calendar}" : "Du oppdaterte hendelse {event} i kalenderen {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} opprettet en oppgave {todo} i listen {calendar}", "You created todo {todo} in list {calendar}" : "Du opprettet en oppgave {todo} i listen {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} slettet en oppgave {todo} fra listen {calendar}", "You deleted todo {todo} from list {calendar}" : "Du slettet en oppgave {todo} fra listen {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} oppdaterte en oppgave {todo} i listen {calendar}", "You updated todo {todo} in list {calendar}" : "Du oppdaterte en oppgave {todo} i listen {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} ferdigstilte oppgaven {todo} i listen {calendar}", "You solved todo {todo} in list {calendar}" : "Du ferdigstilte oppgaven {todo} i listen {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} gjenåpnet oppgaven {todo} i listen {calendar}", "You reopened todo {todo} in list {calendar}" : "Du gjenåpnet oppgaven {todo} i listen {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> ble endret", "A calendar <strong>event</strong> was modified" : "En kalender <strong>hendelse</strong> ble endret", "A calendar <strong>todo</strong> was modified" : "En kalende <strong>gjøremål</strong> ble endret", "Contact birthdays" : "Kontakters fødelsdag", "Personal" : "Personlig", "Contacts" : "Kontakter", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Ekstern adresse: %s", "Request ID: %s" : "Forespørsel ID: %s" }, "nplurals=2; plural=(n != 1);"); l10n/sl.js 0000604 00000000563 15247100403 0006266 0 ustar 00 OC.L10N.register( "dav", { "Contact birthdays" : "Rojstni dnevi kontaktov", "Personal" : "Osebno", "Contacts" : "Stiki", "Technical details" : "Tehnične podrobnosti", "Remote Address: %s" : "Oddaljeni naslov: %s", "Request ID: %s" : "ID Zahteve: %s" }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"); l10n/fi.json 0000604 00000010222 15247100403 0006574 0 ustar 00 { "translations": { "Calendar" : "Kalenteri", "Todos" : "Tehtävät", "{actor} created calendar {calendar}" : "{actor} loi kalenterin {calendar}", "You created calendar {calendar}" : "Loit kalenterin {calendar}", "{actor} deleted calendar {calendar}" : "{actor} poisti kalenterin {calendar}", "You deleted calendar {calendar}" : "Poistit kalenterin {calendar}", "{actor} updated calendar {calendar}" : "{actor} päivitti kalenterin {calendar}", "You updated calendar {calendar}" : "Päivitit kalenterin {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} jakoi kalenterin {calendar} kanssasi", "You shared calendar {calendar} with {user}" : "Jaoit kalenterin {calendar} käyttäjälle {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} jakoi kalenterin {calendar} käyttäjälle {user}", "{actor} unshared calendar {calendar} from you" : "{actor} lopetti kalenterin {calendar} jakamisen kanssasi", "You unshared calendar {calendar} from {user}" : "Lopetit kalenterin {calendar} jakamisen käyttäjälle {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} lopetti kalenterin {calendar} jakamisen käyttäjälle {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} lopetti kalenterin {calendar} jakamisen itselleen", "You shared calendar {calendar} with group {group}" : "Jaoit kalenterin {calendar} ryhmälle {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} jakoi kalenterin {calendar} ryhmälle {group}", "You unshared calendar {calendar} from group {group}" : "Lopetit kalenterin {calendar} jakamisen ryhmälle {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} lopetti kalenterin {calendar} jakamisen ryhmälle {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} loi tapahtuman {event} kalenteriin {calendar}", "You created event {event} in calendar {calendar}" : "Loit tapahtuman {event} kalenteriin {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} poisti tapahtuman {event} kalenterista {calendar}", "You deleted event {event} from calendar {calendar}" : "Poistit tapahtuman {event} kalenterista {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} päivitti tapahtuman {event} kalenteriin {calendar}", "You updated event {event} in calendar {calendar}" : "Päivitit tapahtuman {event} kalenteriin {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} loi tehtävän {todo} listaan {calendar}", "You created todo {todo} in list {calendar}" : "Loit tehtävän {todo} listaan {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} poisti tehtävän {todo} listasta {calendar}", "You deleted todo {todo} from list {calendar}" : "Poistit tehtävän {todo} listasta {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} päivitti tehtävän {todo} listassa {calendar}", "You updated todo {todo} in list {calendar}" : "Päivitit tehtävän {todo} listassa {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} suoritti tehtävän {todo} listasta {calendar}", "You solved todo {todo} in list {calendar}" : "Suoritit tehtävän {todo} listasta {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} avasi uudelleen tehtävän {todo} listassa {calendar}", "You reopened todo {todo} in list {calendar}" : "Avasit uudelleen tehtävän {todo} listassa {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalenteria</strong> on muokattu", "A calendar <strong>event</strong> was modified" : "Kalenterin <strong>tapahtumaa</strong> on muokattu", "A calendar <strong>todo</strong> was modified" : "Kalenterin <strong>tehtävää</strong> on muokattu", "Contact birthdays" : "Yhteystietojen syntymäpäivät", "Personal" : "Henkilökohtainen", "Contacts" : "Yhteystiedot", "Technical details" : "Tekniset yksityiskohdat", "Remote Address: %s" : "Etäosoite: %s", "Request ID: %s" : "Pyynnön tunniste: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/es.js 0000604 00000010505 15247100403 0006254 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendario", "Todos" : "Todos", "{actor} created calendar {calendar}" : "{actor} creó el calendario {calendar}", "You created calendar {calendar}" : "Usted creó el calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} eliminó el calendario {calendar}", "You deleted calendar {calendar}" : "Usted eliminó el calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizó el calendario {calendar}", "You updated calendar {calendar}" : "Usted actualizó el calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} compartió el calendario {calendar} con usted", "You shared calendar {calendar} with {user}" : "Usted compartió el calendario {calendar} con {usuario}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartió el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} dejó de compartir el calendario {calendar} con usted", "You unshared calendar {calendar} from {user}" : "Usted dejó de compartir el calendario {calendar} de {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} dejó de compartir el calendario {calendar} del usuario{user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} dejó de compartir su propio calendario {calendar}", "You shared calendar {calendar} with group {group}" : "Usted compartió el calendario {calendar} con el grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartió el calendario {calendar} con el grupo {group}", "You unshared calendar {calendar} from group {group}" : "Usted dejó de compartir el calendario {calendar} del grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} dejó de compartir el calendario {calendar} del grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creó el evento {event} en el calendario {calendar}", "You created event {event} in calendar {calendar}" : "Usted creó el evento {event} en el calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} eliminó el evento {event} del calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Usted eliminó el evento {event} del calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizó el evento {event} en el calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Usted actualizó el evento {event} en el calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creó la tarea {todo} en la lista {calendar}", "You created todo {todo} in list {calendar}" : "Usted creó la tarea {todo} en la lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} eliminó la tarea {todo} de la lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Usted eliminó la tarea {tod} de la lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizó la tarea {todo }en la lista {calendar}", "You updated todo {todo} in list {calendar}" : "Usted actualizó la tarea {todo} en la lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} completó la tarea {todo} en la lista {calendar}", "You solved todo {todo} in list {calendar}" : "Usted completó la tarea {todo} en la lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabrió la tarea {todo} en la lista {calendar}", "You reopened todo {todo} in list {calendar}" : "Usted reabrió la tarea {todo} en la lista {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> fue modificado.", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> del calendario fue modificado.", "A calendar <strong>todo</strong> was modified" : "Una <strong>lista de tareas</strong> fue modificada", "Contact birthdays" : "Cumpleaños del contacto", "Personal" : "Personal", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de la solicitud: %s" }, "nplurals=2; plural=(n != 1);"); l10n/tr.json 0000604 00000010460 15247100403 0006627 0 ustar 00 { "translations": { "Calendar" : "Takvim", "Todos" : "Yapılacak İşler", "{actor} created calendar {calendar}" : "{actor}, {calendar} takvimini ekledi", "You created calendar {calendar}" : "{calendar} takvimini eklediniz", "{actor} deleted calendar {calendar}" : "{actor}, {calendar} takvimini sildi", "You deleted calendar {calendar}" : "{calendar} takvimini sildiniz", "{actor} updated calendar {calendar}" : "{actor}, {calendar} takvimini güncelledi", "You updated calendar {calendar}" : "{calendar} takvimini güncellediniz", "{actor} shared calendar {calendar} with you" : "{actor}, {calendar} takvimini sizinle paylaştı", "You shared calendar {calendar} with {user}" : "{calendar} takvimini {user} ile paylaştınız", "{actor} shared calendar {calendar} with {user}" : "{actor}, {user} ile {calendar} takvimini paylaştı", "{actor} unshared calendar {calendar} from you" : "{actor}, {calendar} takviminin sizinle paylaşımını kaldırdı", "You unshared calendar {calendar} from {user}" : "{calendar} takviminin {user} ile paylaşımını kaldırdınız", "{actor} unshared calendar {calendar} from {user}" : "{actor}, {calendar} takviminin {user} ile paylaşımını kaldırdı", "{actor} unshared calendar {calendar} from themselves" : "{actor}, {calendar} takviminin kendisi ile paylaşımını kaldırdı", "You shared calendar {calendar} with group {group}" : "{calendar} takvimini {group} grubu ile paylaştınız", "{actor} shared calendar {calendar} with group {group}" : "{actor}, {calendar} takvimini {group} grubu ile paylaştı", "You unshared calendar {calendar} from group {group}" : "{calendar} takviminin {group} grubu ile paylaşımını kaldırdınız", "{actor} unshared calendar {calendar} from group {group}" : "{actor}, {calendar} takviminin {group} grubu ile paylaşımını kaldırdı", "{actor} created event {event} in calendar {calendar}" : "{actor}, {calendar} takvimine {event} etkinliğini ekledi", "You created event {event} in calendar {calendar}" : "{calendar} takvimine {event} etkinliğini eklediniz", "{actor} deleted event {event} from calendar {calendar}" : "{actor}, {calendar} takviminden {event} etkinliğini sildi", "You deleted event {event} from calendar {calendar}" : "{calendar} takviminden {event} etkinliğini sildiniz", "{actor} updated event {event} in calendar {calendar}" : "{actor}, {calendar} takvimindeki {event} etkinliğini güncelledi", "You updated event {event} in calendar {calendar}" : "{calendar} takvimindeki {event} etkinliğini güncellediniz", "{actor} created todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesine {todo} işini ekledi", "You created todo {todo} in list {calendar}" : "{calendar} takvimi listesine {todo} işini eklediniz", "{actor} deleted todo {todo} from list {calendar}" : "{actor}, {calendar} takvimi listesinden {todo} işini sildi", "You deleted todo {todo} from list {calendar}" : "{calendar} takvimi listesinden {todo} işini sildiniz", "{actor} updated todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesindeki {todo} işini güncelledi", "You updated todo {todo} in list {calendar}" : "{calendar} takvimi listesindeki {todo} işini güncellediniz", "{actor} solved todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesindeki {todo} işini tamamladı", "You solved todo {todo} in list {calendar}" : "{calendar} takvimi listesindeki {todo} işini tamamladınız", "{actor} reopened todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesindeki {todo} işini yeniden başlattı", "You reopened todo {todo} in list {calendar}" : "{calendar} takvimi listesindeki {todo} işini yeniden başlattınız", "A <strong>calendar</strong> was modified" : "Bir <strong>takvim</strong> düzenlendi", "A calendar <strong>event</strong> was modified" : "Bir <strong>etkinlik</strong> düzenlendi", "A calendar <strong>todo</strong> was modified" : "Bir takvim <strong>işi</strong> düzenlendi", "Contact birthdays" : "Kişi doğum günleri", "Personal" : "Kişisel", "Contacts" : "Kişiler", "Technical details" : "Teknik ayrıntılar", "Remote Address: %s" : "Uzak Adres: %s", "Request ID: %s" : "İstek Kodu: %s" },"pluralForm" :"nplurals=2; plural=(n > 1);" } l10n/nb.json 0000604 00000010273 15247100403 0006603 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Gjøremål", "{actor} created calendar {calendar}" : "{actor} opprettet kalenderen {calendar}", "You created calendar {calendar}" : "Du opprettet kalenderen {calendar}", "{actor} deleted calendar {calendar}" : "{actor} slettet kalenderen {calendar}", "You deleted calendar {calendar}" : "Du slettet kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} oppdaterte kalenderen {calendar}", "You updated calendar {calendar}" : "Du oppdaterte kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med deg", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} fjernet delingen av kalenderen {calendar} med deg", "You unshared calendar {calendar} from {user}" : "Du fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} fjernet delingen av kalender {calendar} med seg selv", "You shared calendar {calendar} with group {group}" : "Du delte kalender {calendar} med gruppe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delte kalenderen {calendar} med gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Du fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} opprettet en hendelse {event} i kalenderen {calendar}", "You created event {event} in calendar {calendar}" : "Du opprettet en hendelse {event} i kalenderen {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} slettet hendelsen {event} fra kalenderen {calendar}", "You deleted event {event} from calendar {calendar}" : "Du slettet hendelsen {event} fra kalenderen {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} oppdaterte hendelsen {event} i kalenderen {calendar}", "You updated event {event} in calendar {calendar}" : "Du oppdaterte hendelsen {event} i kalenderen {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} opprettet en oppgaven {todo} i listen {calendar}", "You created todo {todo} in list {calendar}" : "Du opprettet en oppgave {todo} i listen {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} slettet gjøremålet {todo} fra listen {calendar}", "You deleted todo {todo} from list {calendar}" : "Du slettet gjøremålet {todo} fra listen {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} oppdaterte gjøremålet {todo} i listen {calendar}", "You updated todo {todo} in list {calendar}" : "Du oppdaterte gjøremålet {todo} i listen {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} ferdigstilte gjøremålet {todo} i listen {calendar}", "You solved todo {todo} in list {calendar}" : "Du ferdigstilte gjøremålet {todo} i listen {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} gjenåpnet gjøremålet {todo} i listen {calendar}", "You reopened todo {todo} in list {calendar}" : "Du gjenåpnet oppgaven {todo} i listen {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> ble endret", "A calendar <strong>event</strong> was modified" : "En kalender <strong>hendelse</strong> ble endret", "A calendar <strong>todo</strong> was modified" : "En kalende <strong>gjøremål</strong> ble endret", "Contact birthdays" : "Kontakters fødelsdag", "Personal" : "Personlig", "Contacts" : "Kontakter", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Ekstern adresse: %s", "Request ID: %s" : "Forespørsel ID: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/lv.json 0000604 00000000561 15247100403 0006624 0 ustar 00 { "translations": { "Contact birthdays" : "Kontaktu dzimšanas dienas", "Personal" : "Personīgi", "Contacts" : "Kontakti", "Technical details" : "Tehniskās detaļas", "Remote Address: %s" : "Attālinātā adrese: %s", "Request ID: %s" : "Pieprasījuma ID: %s" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" } l10n/de_DE.js 0000604 00000010742 15247100403 0006610 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Aufgaben", "{actor} created calendar {calendar}" : "{actor} hat den Kalender {calendar} erstellt", "You created calendar {calendar}" : "Sie haben den Kalender {calendar} erstellt", "{actor} deleted calendar {calendar}" : "{actor} hat den Kalender {calendar} gelöscht", "You deleted calendar {calendar}" : "Sie haben den Kalender {calendar} gelöscht", "{actor} updated calendar {calendar}" : "{actor} hat den Kalender {calendar} aktualisiert", "You updated calendar {calendar}" : "Sie haben hat den Kalender {calendar} aktualisiert", "{actor} shared calendar {calendar} with you" : "{actor} hat den Kalender {calendar} mit Ihnen geteilt", "You shared calendar {calendar} with {user}" : "Sie haben den Kalender {calendar} mit {user} geteilt", "{actor} shared calendar {calendar} with {user}" : "{actor} hat den Kalender {calendar} mit {user} geteilt", "{actor} unshared calendar {calendar} from you" : "{actor} teilt den Kalender {calendar} nicht mehr mit Ihnen", "You unshared calendar {calendar} from {user}" : "Sie teilen den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} teilt den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} teilt den Kalender {calendar} nicht mehr mit sich selbst", "You shared calendar {calendar} with group {group}" : "Sie haben den Kalender {calendar} mit der Gruppe {group} geteilt", "{actor} shared calendar {calendar} with group {group}" : "{actor} teilt den Kalender {calendar} mit der Gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Sie teilen den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} teilt den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} erstellt", "You created event {event} in calendar {calendar}" : "Sie haben das Ereignis {event} im Kalender {calendar} erstellt", "{actor} deleted event {event} from calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} gelöscht", "You deleted event {event} from calendar {calendar}" : "Sie haben das Ereignis {event} im Kalender {calendar} gelöscht", "{actor} updated event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} aktualisiert", "You updated event {event} in calendar {calendar}" : "Sie haben das Ereignis {event} im Kalender {calendar} aktualisiert", "{actor} created todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erstellt", "You created todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} erstellt", "{actor} deleted todo {todo} from list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} gelöscht", "You deleted todo {todo} from list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} gelöscht", "{actor} updated todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} aktualisiert", "You updated todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} aktualisiert", "{actor} solved todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erledigt", "You solved todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} erledigt", "{actor} reopened todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "You reopened todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "A <strong>calendar</strong> was modified" : "Ein <strong>Kalender</strong> wurde bearbeitet", "A calendar <strong>event</strong> was modified" : "Ein Kalender-<strong>Ereignis</strong> wurde bearbeitet", "A calendar <strong>todo</strong> was modified" : "Eine Kalender-<strong>Aufgabe</strong> wurde bearbeitet", "Contact birthdays" : "Geburtstage von Kontakten", "Personal" : "Persönlich", "Contacts" : "Kontakte", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s" }, "nplurals=2; plural=(n != 1);"); l10n/bg.json 0000604 00000011577 15247100403 0006604 0 ustar 00 { "translations": { "Calendar" : "Календар", "Todos" : "Задачи", "{actor} created calendar {calendar}" : "{actor} направи календар {calendar}", "You created calendar {calendar}" : "Направихте календар {calendar}", "{actor} deleted calendar {calendar}" : "{actor} изтри календар {calendar}", "You deleted calendar {calendar}" : "Изтрихте календар {calendar}", "{actor} updated calendar {calendar}" : "{actor} обнови календар {calendar}", "You updated calendar {calendar}" : "Обновихте календар {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} сподели календар {calendar} с теб", "You shared calendar {calendar} with {user}" : "Споделихте календар {calendar} с {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} сподели календар {calendar} с {user}", "{actor} unshared calendar {calendar} from you" : "{actor} отказа споделяне на календар {calendar} с теб", "You unshared calendar {calendar} from {user}" : "Отказахте споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} отказа споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} отказа споделяне на календар {calendar} от себеси", "You shared calendar {calendar} with group {group}" : "Споделихте календар {calendar} с група {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} сподели календар {calendar} с група {group}", "You unshared calendar {calendar} from group {group}" : "Отказахте споделяне на календар {calendar} от група {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} отказа споделяне с календар {calendar} от група {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} създаде събитие {event} в календар {calendar}", "You created event {event} in calendar {calendar}" : "Създадохте събитие {event} в календар {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} изтри събитие {event} от календар {calendar}", "You deleted event {event} from calendar {calendar}" : "Изтрихте събитие {event} от календар {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} обнови събитие {event} в календар {calendar}", "You updated event {event} in calendar {calendar}" : "Обновихте събитие {event} в календар {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} създаде задача {todo} в списък {calendar}", "You created todo {todo} in list {calendar}" : "Създадохте задача {todo} в списък {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} изтри задача {todo} от списък {calendar}", "You deleted todo {todo} from list {calendar}" : "Изтрихте задача {todo} от лист {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} актуализира задача {todo} в списък {calendar}", "You updated todo {todo} in list {calendar}" : "Променихте задача {todo} в списък {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} реши задача {todo} в списък {calendar}", "You solved todo {todo} in list {calendar}" : "Решихте задача {todo} в списък {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} възобнови задача {todo} в списък {calendar}", "You reopened todo {todo} in list {calendar}" : "Възобновихте задача {todo} в списък {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Календар</strong> беше променен", "A calendar <strong>event</strong> was modified" : "Календарно <strong>събитие</strong> беше променено", "A calendar <strong>todo</strong> was modified" : "Календарна <strong>задача</strong> беше променена", "Contact birthdays" : "Рождени дни на контакти", "Personal" : "Личен", "Contacts" : "Контакти", "Technical details" : "Технически детайли", "Remote Address: %s" : "Отдалечен адрес: %s", "Request ID: %s" : "ID на заявка: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/en_GB.json 0000604 00000007707 15247100403 0007166 0 ustar 00 { "translations": { "Calendar" : "Calendar", "Todos" : "Todos", "{actor} created calendar {calendar}" : "{actor} created calendar {calendar}", "You created calendar {calendar}" : "You created calendar {calendar}", "{actor} deleted calendar {calendar}" : "{actor} deleted calendar {calendar}", "You deleted calendar {calendar}" : "You deleted calendar {calendar}", "{actor} updated calendar {calendar}" : "{actor} updated calendar {calendar}", "You updated calendar {calendar}" : "You updated calendar {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} shared calendar {calendar} with you", "You shared calendar {calendar} with {user}" : "You shared calendar {calendar} with {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} shared calendar {calendar} with {user}", "{actor} unshared calendar {calendar} from you" : "{actor} unshared calendar {calendar} from you", "You unshared calendar {calendar} from {user}" : "You unshared calendar {calendar} from {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} unshared calendar {calendar} from {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} unshared calendar {calendar} from themselves", "You shared calendar {calendar} with group {group}" : "You shared calendar {calendar} with group {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} shared calendar {calendar} with group {group}", "You unshared calendar {calendar} from group {group}" : "You unshared calendar {calendar} from group {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} unshared calendar {calendar} from group {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} created event {event} in calendar {calendar}", "You created event {event} in calendar {calendar}" : "You created event {event} in calendar {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} deleted event {event} from calendar {calendar}", "You deleted event {event} from calendar {calendar}" : "You deleted event {event} from calendar {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} updated event {event} in calendar {calendar}", "You updated event {event} in calendar {calendar}" : "You updated event {event} in calendar {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} created todo {todo} in list {calendar}", "You created todo {todo} in list {calendar}" : "You created todo {todo} in list {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} deleted todo {todo} from list {calendar}", "You deleted todo {todo} from list {calendar}" : "You deleted todo {todo} from list {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} updated todo {todo} in list {calendar}", "You updated todo {todo} in list {calendar}" : "You updated todo {todo} in list {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} solved todo {todo} in list {calendar}", "You solved todo {todo} in list {calendar}" : "You solved todo {todo} in list {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reopened todo {todo} in list {calendar}", "You reopened todo {todo} in list {calendar}" : "You reopened todo {todo} in list {calendar}", "A <strong>calendar</strong> was modified" : "A <strong>calendar</strong> was modified", "A calendar <strong>event</strong> was modified" : "A calendar <strong>event</strong> was modified", "A calendar <strong>todo</strong> was modified" : "A calendar <strong>todo</strong> was modified", "Contact birthdays" : "Contact birthdays", "Personal" : "Personal", "Contacts" : "Contacts", "Technical details" : "Technical details", "Remote Address: %s" : "Remote Address: %s", "Request ID: %s" : "Request ID: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/fi.js 0000604 00000010225 15247100403 0006242 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalenteri", "Todos" : "Tehtävät", "{actor} created calendar {calendar}" : "{actor} loi kalenterin {calendar}", "You created calendar {calendar}" : "Loit kalenterin {calendar}", "{actor} deleted calendar {calendar}" : "{actor} poisti kalenterin {calendar}", "You deleted calendar {calendar}" : "Poistit kalenterin {calendar}", "{actor} updated calendar {calendar}" : "{actor} päivitti kalenterin {calendar}", "You updated calendar {calendar}" : "Päivitit kalenterin {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} jakoi kalenterin {calendar} kanssasi", "You shared calendar {calendar} with {user}" : "Jaoit kalenterin {calendar} käyttäjälle {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} jakoi kalenterin {calendar} käyttäjälle {user}", "{actor} unshared calendar {calendar} from you" : "{actor} lopetti kalenterin {calendar} jakamisen kanssasi", "You unshared calendar {calendar} from {user}" : "Lopetit kalenterin {calendar} jakamisen käyttäjälle {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} lopetti kalenterin {calendar} jakamisen käyttäjälle {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} lopetti kalenterin {calendar} jakamisen itselleen", "You shared calendar {calendar} with group {group}" : "Jaoit kalenterin {calendar} ryhmälle {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} jakoi kalenterin {calendar} ryhmälle {group}", "You unshared calendar {calendar} from group {group}" : "Lopetit kalenterin {calendar} jakamisen ryhmälle {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} lopetti kalenterin {calendar} jakamisen ryhmälle {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} loi tapahtuman {event} kalenteriin {calendar}", "You created event {event} in calendar {calendar}" : "Loit tapahtuman {event} kalenteriin {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} poisti tapahtuman {event} kalenterista {calendar}", "You deleted event {event} from calendar {calendar}" : "Poistit tapahtuman {event} kalenterista {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} päivitti tapahtuman {event} kalenteriin {calendar}", "You updated event {event} in calendar {calendar}" : "Päivitit tapahtuman {event} kalenteriin {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} loi tehtävän {todo} listaan {calendar}", "You created todo {todo} in list {calendar}" : "Loit tehtävän {todo} listaan {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} poisti tehtävän {todo} listasta {calendar}", "You deleted todo {todo} from list {calendar}" : "Poistit tehtävän {todo} listasta {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} päivitti tehtävän {todo} listassa {calendar}", "You updated todo {todo} in list {calendar}" : "Päivitit tehtävän {todo} listassa {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} suoritti tehtävän {todo} listasta {calendar}", "You solved todo {todo} in list {calendar}" : "Suoritit tehtävän {todo} listasta {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} avasi uudelleen tehtävän {todo} listassa {calendar}", "You reopened todo {todo} in list {calendar}" : "Avasit uudelleen tehtävän {todo} listassa {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalenteria</strong> on muokattu", "A calendar <strong>event</strong> was modified" : "Kalenterin <strong>tapahtumaa</strong> on muokattu", "A calendar <strong>todo</strong> was modified" : "Kalenterin <strong>tehtävää</strong> on muokattu", "Contact birthdays" : "Yhteystietojen syntymäpäivät", "Personal" : "Henkilökohtainen", "Contacts" : "Yhteystiedot", "Technical details" : "Tekniset yksityiskohdat", "Remote Address: %s" : "Etäosoite: %s", "Request ID: %s" : "Pyynnön tunniste: %s" }, "nplurals=2; plural=(n != 1);"); l10n/fi_FI.js 0000604 00000000534 15247100403 0006622 0 ustar 00 OC.L10N.register( "dav", { "Contact birthdays" : "Yhteystietojen syntymäpäivät", "Personal" : "Henkilökohtainen", "Contacts" : "Yhteystiedot", "Technical details" : "Tekniset yksityiskohdat", "Remote Address: %s" : "Etäosoite: %s", "Request ID: %s" : "Pyynnön tunniste: %s" }, "nplurals=2; plural=(n != 1);"); l10n/de.json 0000604 00000010673 15247100403 0006600 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Aufgaben", "{actor} created calendar {calendar}" : "{actor} hat den Kalender {calendar} erstellt", "You created calendar {calendar}" : "Du hast den Kalender {calendar} erstellt", "{actor} deleted calendar {calendar}" : "{actor} hat den Kalender {calendar} gelöscht", "You deleted calendar {calendar}" : "Du hast den Kalender {calendar} gelöscht", "{actor} updated calendar {calendar}" : "{actor} hat den Kalender {calendar} aktualisiert", "You updated calendar {calendar}" : "Du hast den Kalender {calendar} aktualisiert", "{actor} shared calendar {calendar} with you" : "{actor} hat den Kalender {calendar} mit Dir geteilt", "You shared calendar {calendar} with {user}" : "Du hast den Kalender {calendar} mit {user} geteilt", "{actor} shared calendar {calendar} with {user}" : "{actor} hat den Kalender {calendar} mit {user} geteilt", "{actor} unshared calendar {calendar} from you" : "{actor} teilt den Kalender {calendar} nicht mehr mit Dir", "You unshared calendar {calendar} from {user}" : "Du teilst den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} teilt den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} teilt den Kalender {calendar} nicht mehr mit sich selbst", "You shared calendar {calendar} with group {group}" : "Du hast den Kalender {calendar} mit der Gruppe {group} geteilt", "{actor} shared calendar {calendar} with group {group}" : "{actor} teilt den Kalender {calendar} mit der Gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Du teilst den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} teilt den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} erstellt", "You created event {event} in calendar {calendar}" : "Du hast das Ereignis {event} im Kalender {calendar} erstellt", "{actor} deleted event {event} from calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} gelöscht", "You deleted event {event} from calendar {calendar}" : "Du hast das Ereignis {event} im Kalender {calendar} gelöscht", "{actor} updated event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} aktualisiert", "You updated event {event} in calendar {calendar}" : "Du hast das Ereignis {event} im Kalender {calendar} aktualisiert", "{actor} created todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erstellt", "You created todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} erstellt", "{actor} deleted todo {todo} from list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} gelöscht", "You deleted todo {todo} from list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} gelöscht", "{actor} updated todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} aktualisiert", "You updated todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} aktualisiert", "{actor} solved todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erledigt", "You solved todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} erledigt", "{actor} reopened todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "You reopened todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "A <strong>calendar</strong> was modified" : "Ein <strong>Kalender</strong> wurde bearbeitet", "A calendar <strong>event</strong> was modified" : "Ein Kalender-<strong>Ereignis</strong> wurde bearbeitet", "A calendar <strong>todo</strong> was modified" : "Eine Kalender-<strong>Aufgabe</strong> wurde bearbeitet", "Contact birthdays" : "Geburtstage von Kontakten", "Personal" : "Persönlich", "Contacts" : "Kontakte", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/ro.js 0000604 00000010575 15247100403 0006274 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendar", "Todos" : "De făcut", "{actor} created calendar {calendar}" : "{actor} a creat calendarul {calendar}", "You created calendar {calendar}" : "Ai creat calendarul {calendar}", "{actor} deleted calendar {calendar}" : "{actor} a șters calendarul {calendar}", "You deleted calendar {calendar}" : "Ai șters calendarul {calendar}", "{actor} updated calendar {calendar}" : "{actor} a actualiza calendarul {calendar}", "You updated calendar {calendar}" : "Ai actualizat calendarul {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} a partajat calendarul {calendar} cu tine", "You shared calendar {calendar} with {user}" : "Ai partajat calendarul {calendar} cu {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} a partajat calendarul {calendar} cu {user}", "{actor} unshared calendar {calendar} from you" : "{actor} a eliminat partajarea calendarului {calendar} cu tine", "You unshared calendar {calendar} from {user}" : "Ai eliminat partajarea calendarului {calendar} cu {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} a eliminat partajarea calendarului {calendar} cu {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} a eliminat partajarea calendarului {calendar} catre sine", "You shared calendar {calendar} with group {group}" : "Ai partajat calendarul {calendar} cu grupul {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} a partajat calendarul {calendar} cu grupul {group}", "You unshared calendar {calendar} from group {group}" : "Ai eliminat partajarea calendarului {calendar} către grupul {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} a elimina partajarea calendarului {calendar} către grupul {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} a creat evenimentul {event} în calendarul {calendar}", "You created event {event} in calendar {calendar}" : "Ai creat evenimentul {event} în calendarul {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} a șters evenimentul {event} din calendarul {calendar}", "You deleted event {event} from calendar {calendar}" : "AI șters evenimentul {event} din calendarul {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} a actualizat evenimentul {event} din calendarul {calendar}", "You updated event {event} in calendar {calendar}" : "Ai actualizat evenimentul {event} din calendarul {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} a creat lista {todo} în calendarul {calendar}", "You created todo {todo} in list {calendar}" : "Ai creat lista {todo} în lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} a șters lista {todo} din calendarul {calendar}", "You deleted todo {todo} from list {calendar}" : "Ai șters lista {todo} din calendarul {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} a actualizat lista {todo} din calendarul {calendar}", "You updated todo {todo} in list {calendar}" : "Ai actualizat lista {todo} din calendarul {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} a completat lista {todo} din calendarul {calendar}", "You solved todo {todo} in list {calendar}" : "Ai completat lista {todo} din calendarul {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} a redeschis lista {todo} din calendarul {calendar}", "You reopened todo {todo} in list {calendar}" : "Ai redeschis lista {todo} din calendarul {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendar</strong> a fost modificat", "A calendar <strong>event</strong> was modified" : "Un <strong>eveniment</strong> din calendar a fost modificat", "A calendar <strong>todo</strong> was modified" : "O <strong>listă</strong> din calendar a fost modificată", "Contact birthdays" : "Zile de naștere ale persoanelor de contact", "Personal" : "Personal", "Contacts" : "Persoane de contact", "Technical details" : "Detalii tehnice", "Remote Address: %s" : "Adresă la distanță: %s", "Request ID: %s" : "ID-ul cererii: %s" }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); l10n/ru.json 0000604 00000012202 15247100403 0006624 0 ustar 00 { "translations": { "Calendar" : "Календарь", "Todos" : "Задачи", "{actor} created calendar {calendar}" : "{actor} создал календарь {calendar}", "You created calendar {calendar}" : "Вы создали календарь {calendar}", "{actor} deleted calendar {calendar}" : "{actor} удалил календарь {calendar}", "You deleted calendar {calendar}" : "Вы удалили календарь {calendar}", "{actor} updated calendar {calendar}" : "{actor} обновил календарь {calendar}", "You updated calendar {calendar}" : "Вы обновили календарь {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} поделился календарем {calendar} с вами", "You shared calendar {calendar} with {user}" : "Вы поделились календарем {calendar} с {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} поделился календарем {calendar} с {user}", "{actor} unshared calendar {calendar} from you" : "{actor} закрыл для вас доступ к календарю {calendar}", "You unshared calendar {calendar} from {user}" : "Вы закрыли доступ к календарю {calendar} для {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} закрыл доступ к календарю {calendar} для {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} закрыл доступ к календарю {calendar} для себя", "You shared calendar {calendar} with group {group}" : "Вы поделились календарем {calendar} с группой {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} поделился календарем {calendar} с группой {group}", "You unshared calendar {calendar} from group {group}" : "Вы закрыли доступ к календарю {calendar} для группы {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} закрыл доступ к календарю {calendar} для группы {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} создал событие {event} в календаре {calendar}", "You created event {event} in calendar {calendar}" : "Вы создали событие {event} в календаре {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} удалил событие {event} из календаря {calendar}", "You deleted event {event} from calendar {calendar}" : "Вы удалили событие {event} из календаря {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} обновил событие {event} в календаре {calendar}", "You updated event {event} in calendar {calendar}" : "Вы обновили событие {event} в календаре {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} создал задачу {todo} в списке {calendar}", "You created todo {todo} in list {calendar}" : "Вы создали задачу {todo} в списке {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} удалил задачу {todo} из списка {calendar}", "You deleted todo {todo} from list {calendar}" : "Вы удалили задачу {todo} из списка {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} обновил задачу {todo} из списка {calendar}", "You updated todo {todo} in list {calendar}" : "Вы обновили задачу {todo} из списка {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} завершил задачу {todo} из списка {calendar}", "You solved todo {todo} in list {calendar}" : "Вы завершили задачу {todo} из списка {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} повторно открыл задачу {todo} из списка {calendar}", "You reopened todo {todo} in list {calendar}" : "Вы повторно открыли задачу {todo} из списка {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Календарь</strong> был изменен", "A calendar <strong>event</strong> was modified" : "<strong>Событие</strong> календаря была изменена", "A calendar <strong>todo</strong> was modified" : "<strong>Задача</strong> календаря была изменена", "Contact birthdays" : "Дни рождения контакта", "Personal" : "Личное", "Contacts" : "Контакты", "Technical details" : "Технические подробности", "Remote Address: %s" : "Удаленный адрес: %s", "Request ID: %s" : "ID запроса: %s" },"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" } l10n/cs_CZ.js 0000604 00000010520 15247100403 0006643 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalendář", "Todos" : "Úkoly", "{actor} created calendar {calendar}" : "{actor} vytvořil(a) kalendář {calendar}", "You created calendar {calendar}" : "Vytvořil(a", "{actor} deleted calendar {calendar}" : "{actor} smazal(a) kalendář {calendar}", "You deleted calendar {calendar}" : "Smazal(a) jste kalendář {calendar}", "{actor} updated calendar {calendar}" : "{actor} aktualizoval(a) kalendář {calendar}", "You updated calendar {calendar}" : "Aktualizoval(a) jste kalendář {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} s vámi nasdílel(a) kalendář {calendar}", "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} začal sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from you" : "{actor} s vámi přestal(a) sdílet kalendář {calendar}", "You unshared calendar {calendar} from {user}" : "S uživatelem {user} jste přestal(a) sdílet kalendář {calendar}", "{actor} unshared calendar {calendar} from {user}" : "{actor} přestal(a) sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} přestal sdílet kalendář {calendar} sám se sebou", "You shared calendar {calendar} with group {group}" : "Se skupinou {group} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with group {group}" : "{actor} nasdílel(a) kalendář {calendar} skupině {group}", "You unshared calendar {calendar} from group {group}" : "Zrušil(a) jste sdílení kalendáře {calendar} skupině {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} přestal(a) sdílet kalendář {calendar} se skupinou {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} vytvořil(a) událost {event} v kalendáři {calendar}", "You created event {event} in calendar {calendar}" : "V kalendáři {calendar} jste vytvořil(a) událost {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} z kalendáře {calendar} smazal(a) událost {event}", "You deleted event {event} from calendar {calendar}" : "Smazal(a) jste událost {event} z kalendáře {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} aktualizoval(a) událost {event} v kalendáři {calendar}", "You updated event {event} in calendar {calendar}" : "Aktualizoval(a) jste událost {event} v kalendáři {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} vytvořil(a) v seznamu {calendar} vytvořila úkol {todo}", "You created todo {todo} in list {calendar}" : "V seznamu {calendar} jste vytvořil(a) úkol {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} smazal(a) úkol {todo} ze seznamu {calendar}", "You deleted todo {todo} from list {calendar}" : "Ze seznamu {todo} jste smazal(a) úkol {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} aktualizoval(a) úkol {todo} v seznamu {calendar}", "You updated todo {todo} in list {calendar}" : "Aktualizoval(a) jste úkol {todo} v seznamu {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} vyřešil(a) úkol {todo} v seznamu {calendar}", "You solved todo {todo} in list {calendar}" : "Vyřešil(a) jste úkol {todo} v seznamu {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} znovu otevřel(a) úkol {todo} v seznamu {calendar}", "You reopened todo {todo} in list {calendar}" : "Znovu jste otevřel(a) úkol {todo} v seznamu {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalendář</strong> byl změněn", "A calendar <strong>event</strong> was modified" : "<strong>Událost</strong> v kalendáři byla změněna", "A calendar <strong>todo</strong> was modified" : "<strong>Úkol</strong> v kalendáři byl změněn", "Contact birthdays" : "Narozeniny kontaktů", "Personal" : "Osobní", "Contacts" : "Kontakty", "Technical details" : "Technické detaily", "Remote Address: %s" : "Vzdálená adresa: %s", "Request ID: %s" : "ID požadavku: %s" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"); l10n/it.json 0000604 00000010733 15247100403 0006621 0 ustar 00 { "translations": { "Calendar" : "Calendario", "Todos" : "Cose da fare", "{actor} created calendar {calendar}" : "{actor} ha creato il calendario {calendar}", "You created calendar {calendar}" : "Hai creato il calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} ha eliminato il calendario {calendar}", "You deleted calendar {calendar}" : "Hai eliminato il calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} ha aggiornato il calendario {calendar}", "You updated calendar {calendar}" : "Hai aggiornato il calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} ha condiviso il calendario {calendar} con te", "You shared calendar {calendar} with {user}" : "Hai condiviso il calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} ha aggiornato il calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ha rimosso la condivisione del calendario {calendar} con te", "You unshared calendar {calendar} from {user}" : "Hai rimosso la condivisione del calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} ha rimosso la condivisione del calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} ha rimosso la condivisione del calendario {calendar} con se stesso", "You shared calendar {calendar} with group {group}" : "Hai condiviso il calendario {calendar} con il gruppo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} ha condiviso il calendario {calendar} con il gruppo {group}", "You unshared calendar {calendar} from group {group}" : "Hai rimosso la condivisione del calendario {calendar} con il gruppo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} ha rimosso la condivisione del calendario {calendar} con il gruppo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} ha creato l'evento {event} nel calendario {calendar}", "You created event {event} in calendar {calendar}" : "Hai creato l'evento {event} nel calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} ha eliminato l'evento {event} dal calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Hai ha eliminato l'evento {event} dal calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} ha aggiornato l'evento {event} nel calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Hai aggiornato l'evento {event} nel calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} ha creato la cosa da fare {todo} nell'elenco {calendar}", "You created todo {todo} in list {calendar}" : "Hai creato la cosa da fare {todo} nell'elenco {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} ha eliminato la cosa da fare {todo} dall'elenco {calendar}", "You deleted todo {todo} from list {calendar}" : "Hai eliminato la cosa da fare {todo} dall'elenco {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} ha aggiornato la cosa da fare {todo} nell'elenco {calendar}", "You updated todo {todo} in list {calendar}" : "Hai aggiornato la cosa da fare {todo} nell'elenco {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} ha risolto la cosa da fare {todo} nell'elenco {calendar}", "You solved todo {todo} in list {calendar}" : "Hai risolto la cosa da fare {todo} nell'elenco {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} ha riaperto la cosa da fare {todo} nell'elenco {calendar}", "You reopened todo {todo} in list {calendar}" : "Hai riaperto la cosa da fare {todo} nell'elenco {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> è stato modificato", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> del calendario è stato modificato", "A calendar <strong>todo</strong> was modified" : "Una <strong>cosa da fare</strong> del calendario è stata modificata", "Contact birthdays" : "Date di nascita dei contatti", "Personal" : "Personale", "Contacts" : "Contatti", "Technical details" : "Dettagli tecnici", "Remote Address: %s" : "Indirizzo remoto: %s", "Request ID: %s" : "ID richiesta: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/lv.js 0000604 00000000564 15247100403 0006272 0 ustar 00 OC.L10N.register( "dav", { "Contact birthdays" : "Kontaktu dzimšanas dienas", "Personal" : "Personīgi", "Contacts" : "Kontakti", "Technical details" : "Tehniskās detaļas", "Remote Address: %s" : "Attālinātā adrese: %s", "Request ID: %s" : "Pieprasījuma ID: %s" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"); l10n/pt_BR.js 0000604 00000010376 15247100403 0006661 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendário", "Todos" : "Tarefas", "{actor} created calendar {calendar}" : "{actor} criou o calendário {calendar}", "You created calendar {calendar}" : "Você criou o calendário {calendar}", "{actor} deleted calendar {calendar}" : "{actor} excluiu o calendário {calendar}", "You deleted calendar {calendar}" : "Você excluiu o calendário {calendar}", "{actor} updated calendar {calendar}" : "{actor} atualizou o calendário {calendar}", "You updated calendar {calendar}" : "Você atualizou o calendário {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} compartilhou o calendário {calendar} com você", "You shared calendar {calendar} with {user}" : "Você compartilhou o calendário {calendar} com {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartilhou o calendário {calendar} com {user}", "{actor} unshared calendar {calendar} from you" : "{actor} descompartilhou o calendário {calendar} com você", "You unshared calendar {calendar} from {user}" : "Você descompartilhou o calendário {calendar} com {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} descompartilhou o calendário {calendar} de {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} descompartilhou o calendário {calendar} deles mesmos", "You shared calendar {calendar} with group {group}" : "Você compartilhou o calendário {calendar} com o grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartilhou o calendário {calendar} com o grupo {group}", "You unshared calendar {calendar} from group {group}" : "Você descompartilhou o calendário {calendar} do grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} descompartilhou o calendário {calendar} do grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} criou o evento {event} no calendário {calendar}", "You created event {event} in calendar {calendar}" : "Você criou o evento {event} no calendário {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} excluiu o evento {event} do calendário {calendar}", "You deleted event {event} from calendar {calendar}" : "Você excluiu o evento {event} do calendário {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} atualizou o evento {event} no calendário {calendar}", "You updated event {event} in calendar {calendar}" : "Você atualizou o evento {event} no calendário {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} criou a tarefa {todo} na lista {calendar}", "You created todo {todo} in list {calendar}" : "Você criou a tarefa {todo} na lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} excluiu a tarefa {todo} da lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Você excluiu a tarefa {todo} da lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} atualizou a tarefa {todo} na lista {calendar}", "You updated todo {todo} in list {calendar}" : "Você atualizou a tarefa {todo} na lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} tarefa resolvida {todo} na lista {calendar}", "You solved todo {todo} in list {calendar}" : "Você terminou a tarefa {todo} na lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabriu tarefa {todo} na lista {calendar}", "You reopened todo {todo} in list {calendar}" : "Você reabriu a terefa {todo} na lista {calendar}", "A <strong>calendar</strong> was modified" : "Um <strong>calendário</strong> foi modificado", "A calendar <strong>event</strong> was modified" : "Um <strong>evento</strong> do calendário foi modificado", "A calendar <strong>todo</strong> was modified" : "Uma <strong>tarefa</strong> do calendário foi modificada", "Contact birthdays" : "Aniversário dos contatos", "Personal" : "Pessoal", "Contacts" : "Contatos", "Technical details" : "Detalhes técnicos", "Remote Address: %s" : "Endereço remoto: %s", "Request ID: %s" : "ID do solicitante: %s" }, "nplurals=2; plural=(n > 1);"); l10n/es_MX.json 0000604 00000010464 15247100403 0007221 0 ustar 00 { "translations": { "Calendar" : "Calendario", "Todos" : "Pendientes", "{actor} created calendar {calendar}" : "{actor} creó el calendario {calendar}", "You created calendar {calendar}" : "Creaste el calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} borró el calendario {calendar}", "You deleted calendar {calendar}" : "Borraste el calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizó el calendario {calendar}", "You updated calendar {calendar}" : "Actualizaste el calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} ha compartido el calendario {calendar} contigo", "You shared calendar {calendar} with {user}" : "Compartiste el calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartió el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ha dejado de compartir el calendario {calendar} contigo", "You unshared calendar {calendar} from {user}" : "Has dejado de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} dejó de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} dejó de compartir {el calendario calendar} con él mismo", "You shared calendar {calendar} with group {group}" : "Compartiste el calendario {calendar} con el grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartió el calendario {calendar} con el grupo {group}", "You unshared calendar {calendar} from group {group}" : "Dejaste de compartir el calendario {calendar} con el grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} dejó de compartir el calendrio {calendar} con el grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creó el evento {event} en el calendario {calendar}", "You created event {event} in calendar {calendar}" : "Creaste el evento {event} en el calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} borró el eventó {event} del calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Borraste el evento {event} del calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizó el evento {event} en el calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Actualizaste el evento {event} en el calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creó el pendiente {todo} en la lista {calendar}", "You created todo {todo} in list {calendar}" : "Creaste el pendiente {todo} en la lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} borró el pendiente {todo} de la lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Borraste el pendiente {todo} de la lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizó el pendiente {todo} de la lista {calendar}", "You updated todo {todo} in list {calendar}" : "Actualizaste el pendiente {todo} de la lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} resolvió el pendiente {todo} de la lista {calendar}", "You solved todo {todo} in list {calendar}" : "Resolviste el pendiente {todo} de la lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabrió el pendiente {todo} de la lista{calendar}", "You reopened todo {todo} in list {calendar}" : "Reabriste el pendiente {todo} de la lista {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> fue modificado", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> de un calendario fue modificado", "A calendar <strong>todo</strong> was modified" : "Un <strong>pendiente</strong> de un calendario fue modificado", "Contact birthdays" : "Cumpleaños del contacto", "Personal" : "Personal", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/da.json 0000604 00000010203 15247100403 0006561 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Opgaver", "{actor} created calendar {calendar}" : "{actor} oprettede kalenderen {calendar}", "You created calendar {calendar}" : "Du oprettede kalenderen {calendar}", "{actor} deleted calendar {calendar}" : "{actor} sletede kalenderen {calendar}", "You deleted calendar {calendar}" : "Du slettede kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} opdaterede kalenderen {calendar}", "You updated calendar {calendar}" : "Du opdaterede kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med dig", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} fjernede delingen af {calendar} fra dig", "You unshared calendar {calendar} from {user}" : "Du fjernede delingen af {calendar} fra {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} fjernede delingen af {calendar} fra {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} fjernede delingen af {calendar} fra sig selv", "You shared calendar {calendar} with group {group}" : "Du delte kalenderen {calendar} med gruppen {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delte kalenderen {calendar} med gruppen {group}", "You unshared calendar {calendar} from group {group}" : "Du fjernede delingen af {calendar} fra gruppen {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} fjernede deling af kalenderen {calendar} fra gruppen {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} oprettede begivenheden {event} i kalenderen {calendar}", "You created event {event} in calendar {calendar}" : "Du oprettede begivenheden {event} i kalenderen {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} slettede begivenheden {event} fra kalenderen {calendar}", "You deleted event {event} from calendar {calendar}" : "Du slettede begivenheden {event} fra kalenderen {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} opdaterede begivenheden {event} i kalenderen {calendar}", "You updated event {event} in calendar {calendar}" : "Du opdaterede begivenheden {event} i kalenderen {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} oprettede en opgave {todo} i listen {calendar}", "You created todo {todo} in list {calendar}" : "Du oprettede opgaven {todo} i listen {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} slettede opgaven {todo} fra listen {calendar}", "You deleted todo {todo} from list {calendar}" : "Du slettede opgaven {todo} fra listen {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} opdaterede opgaven {todo} i listen {calendar}", "You updated todo {todo} in list {calendar}" : "Du opdaterede opgaven {todo} i listen {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} løste opgaven {todo} i listen {calendar}", "You solved todo {todo} in list {calendar}" : "Du løste opgaven {todo} i listen {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} genåbnede opgaven {todo} i listen {calendar}", "You reopened todo {todo} in list {calendar}" : "Du genåbnede opgaven {todo} i listen {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> er blevet ændret", "A calendar <strong>event</strong> was modified" : "En kalender <strong>begivenhed</strong> er blevet ændret", "A calendar <strong>todo</strong> was modified" : "En kalender <strong>opgave</strong> blev ændret", "Contact birthdays" : "Kontakt fødselsdag", "Personal" : "Personligt", "Contacts" : "Kontakter", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Fjernadresse: %s", "Request ID: %s" : "Forespørgsels-ID: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/sk.js 0000604 00000010154 15247100403 0006262 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalendár", "Todos" : "Úlohy", "{actor} created calendar {calendar}" : "[actor] vytvoril kalendár [calendar]", "You created calendar {calendar}" : "Vytvorili ste kalendár [calendar]", "{actor} deleted calendar {calendar}" : "[actor] zmazal kalendár [calendar]", "You deleted calendar {calendar}" : "Zmazali ste kalendár [calendar]", "{actor} updated calendar {calendar}" : "[actor] upravil kalendár [calendar]", "You updated calendar {calendar}" : "Upravili ste kalendár [calendar]", "{actor} shared calendar {calendar} with you" : "{actor} vám sprístupnil kalendár {calendar}", "You shared calendar {calendar} with {user}" : "Sprístupnili ste kalendár {calendar} s {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} sprístupnil kalendár {calendar} s {user}", "{actor} unshared calendar {calendar} from you" : "{actor} vám prestal sprístupňovať kalendár {calendar}", "You unshared calendar {calendar} from {user}" : "Prestali ste sprístupňovať kalendár {calendar} od používateľa {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} zrušil zdieľanie kalendára {calendar} s používateľom {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} zrušil zdieľanie kalendára {calendar} so sebou samým", "You shared calendar {calendar} with group {group}" : "Sprístupnili ste kalendár {calendar} so skupinou {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} vyzdieľal kalendár {calendar} so skupinou {group}", "You unshared calendar {calendar} from group {group}" : "Zrušili ste zdieľanie kalendára {calendar} so skupinou {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} zrušil zdieľanie kalendára {calendar} so skupinou {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} vytvoril udalosť {event} v kalendári {calendar}", "You created event {event} in calendar {calendar}" : "Vytvorili ste udalosť [event] v kalendári [calendar]", "{actor} deleted event {event} from calendar {calendar}" : "[actor] zmazal udalosť [event] z kalendára [calendar]", "You deleted event {event} from calendar {calendar}" : "Zmazali ste udalosť [event] z kalendára [calendar]", "{actor} updated event {event} in calendar {calendar}" : "{actor} aktualizoval udalosť {event} v kalendári {calendar}", "You updated event {event} in calendar {calendar}" : "Aktualizovali ste udalosť {event} v kalendári {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} vytvoril úlohu {todo} v {calendar}", "You created todo {todo} in list {calendar}" : "Vytvorili ste úlohu {todo} v {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} zmazal úlohu {todo} z {calendar}", "You deleted todo {todo} from list {calendar}" : "Zmazali ste úlohu {todo} z {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} upravil úlohu {todo} v {calendar}", "You updated todo {todo} in list {calendar}" : "Upravili ste úlohu {todo} v {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} vyriešil úlohu {todo} v {calendar}", "You solved todo {todo} in list {calendar}" : "Vyriešili ste úlohu {todo} v {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} znovu otvoril úlohu {todo} v {calendar}", "You reopened todo {todo} in list {calendar}" : "Otvorili ste znovu úlohu {todo} v {calendar}", "A <strong>calendar</strong> was modified" : "<strong>kalendár</strong> bol upravený", "A calendar <strong>event</strong> was modified" : "<strong>Udalosť</strong> v kalendári bola upravená", "A calendar <strong>todo</strong> was modified" : "<>", "Contact birthdays" : "Narodeniny kontaktu", "Personal" : "Osobné", "Contacts" : "Kontakty", "Technical details" : "Technické podrobnosti", "Remote Address: %s" : "Vzdialená adresa: %s", "Request ID: %s" : "ID požiadavky: %s" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"); l10n/de_DE.json 0000604 00000010737 15247100403 0007151 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Aufgaben", "{actor} created calendar {calendar}" : "{actor} hat den Kalender {calendar} erstellt", "You created calendar {calendar}" : "Sie haben den Kalender {calendar} erstellt", "{actor} deleted calendar {calendar}" : "{actor} hat den Kalender {calendar} gelöscht", "You deleted calendar {calendar}" : "Sie haben den Kalender {calendar} gelöscht", "{actor} updated calendar {calendar}" : "{actor} hat den Kalender {calendar} aktualisiert", "You updated calendar {calendar}" : "Sie haben hat den Kalender {calendar} aktualisiert", "{actor} shared calendar {calendar} with you" : "{actor} hat den Kalender {calendar} mit Ihnen geteilt", "You shared calendar {calendar} with {user}" : "Sie haben den Kalender {calendar} mit {user} geteilt", "{actor} shared calendar {calendar} with {user}" : "{actor} hat den Kalender {calendar} mit {user} geteilt", "{actor} unshared calendar {calendar} from you" : "{actor} teilt den Kalender {calendar} nicht mehr mit Ihnen", "You unshared calendar {calendar} from {user}" : "Sie teilen den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} teilt den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} teilt den Kalender {calendar} nicht mehr mit sich selbst", "You shared calendar {calendar} with group {group}" : "Sie haben den Kalender {calendar} mit der Gruppe {group} geteilt", "{actor} shared calendar {calendar} with group {group}" : "{actor} teilt den Kalender {calendar} mit der Gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Sie teilen den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} teilt den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} erstellt", "You created event {event} in calendar {calendar}" : "Sie haben das Ereignis {event} im Kalender {calendar} erstellt", "{actor} deleted event {event} from calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} gelöscht", "You deleted event {event} from calendar {calendar}" : "Sie haben das Ereignis {event} im Kalender {calendar} gelöscht", "{actor} updated event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} aktualisiert", "You updated event {event} in calendar {calendar}" : "Sie haben das Ereignis {event} im Kalender {calendar} aktualisiert", "{actor} created todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erstellt", "You created todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} erstellt", "{actor} deleted todo {todo} from list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} gelöscht", "You deleted todo {todo} from list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} gelöscht", "{actor} updated todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} aktualisiert", "You updated todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} aktualisiert", "{actor} solved todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erledigt", "You solved todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} erledigt", "{actor} reopened todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "You reopened todo {todo} in list {calendar}" : "Sie haben die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "A <strong>calendar</strong> was modified" : "Ein <strong>Kalender</strong> wurde bearbeitet", "A calendar <strong>event</strong> was modified" : "Ein Kalender-<strong>Ereignis</strong> wurde bearbeitet", "A calendar <strong>todo</strong> was modified" : "Eine Kalender-<strong>Aufgabe</strong> wurde bearbeitet", "Contact birthdays" : "Geburtstage von Kontakten", "Personal" : "Persönlich", "Contacts" : "Kontakte", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/lt_LT.js 0000604 00000010610 15247100403 0006660 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalendorius", "Todos" : "Užduotys", "{actor} created calendar {calendar}" : "{actor} sukūrė kalendorių {calendar}", "You created calendar {calendar}" : "Jūs sukūrėte kalendorių {calendar}", "{actor} deleted calendar {calendar}" : "{actor} ištrynė kalendorių {calendar}", "You deleted calendar {calendar}" : "Jūs ištrynėte kalendorių {calendar}", "{actor} updated calendar {calendar}" : "{actor} atnaujino kalendorių {calendar}", "You updated calendar {calendar}" : "Jūs atnaujinote kalendorių {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} pradėjo bendrinti su jumis kalendorių {calendar}", "You shared calendar {calendar} with {user}" : "Jūs pradėjote bendrinti kalendorių {calendar} su {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} pradėjo bendrinti kalendorių {calendar} su {user}", "{actor} unshared calendar {calendar} from you" : "{actor} nustojo bendrinti su jumis kalendorių {calendar}", "You unshared calendar {calendar} from {user}" : "Jūs nustojote bendrinti kalendorių {calendar} su {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} nustojo bendrinti kalendorių {calendar} su {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} nustojo bendrinti su savimi kalendorių {calendar}", "You shared calendar {calendar} with group {group}" : "Jūs pradėjote bendrinti kalendorių {calendar} su grupe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} pradėjo bendrinti kalendorių {calendar} su grupe {group}", "You unshared calendar {calendar} from group {group}" : "Jūs nustojote bendrinti kalendorių {calendar} su grupe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} nustojo bendrinti kalendorių {calendar} su grupe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} kalendoriuje {calendar} sukūrė įvykį {event}", "You created event {event} in calendar {calendar}" : "Jūs kalendoriuje {calendar} sukūrėte įvykį {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} ištrynė įvykį {event} iš kalendoriaus {calendar}", "You deleted event {event} from calendar {calendar}" : "Jūs ištrynėte įvykį {event} iš kalendoriaus {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} kalendoriuje {calendar} atnaujino įvykį {event}", "You updated event {event} in calendar {calendar}" : "Jūs kalendoriuje {calendar} atnaujinote įvykį {event}", "{actor} created todo {todo} in list {calendar}" : "{actor} sąraše {calendar} sukūrė užduotį {todo}", "You created todo {todo} in list {calendar}" : "Jūs sąraše {calendar} sukūrėte užduotį {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} iš sąrašo {calendar} ištrynė užduotį {todo}", "You deleted todo {todo} from list {calendar}" : "Jūs iš sąrašo {calendar} ištrynėte užduotį {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} sąraše {calendar} atnaujino užduotį {todo}", "You updated todo {todo} in list {calendar}" : "Jūs sąraše {calendar} atnaujinote užduotį {todo}", "{actor} solved todo {todo} in list {calendar}" : "{actor} sąraše {calendar} išsprendė užduotį {todo}", "You solved todo {todo} in list {calendar}" : "Jūs sąraše {calendar} išsprendėte užduotį {todo}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} sąraše {calendar} vėl pradėjo užduotį {todo}", "You reopened todo {todo} in list {calendar}" : "Jūs sąraše {calendar} vėl pradėjote užduotį {todo}", "A <strong>calendar</strong> was modified" : "<strong>Kalendorius</strong> buvo pakeistas", "A calendar <strong>event</strong> was modified" : "Kalendoriaus <strong>įvykis</strong> buvo pakeistas", "A calendar <strong>todo</strong> was modified" : "Kalendoriaus <strong>užduotis</strong> buvo pakeista", "Contact birthdays" : "Kontaktų gimtadieniai", "Personal" : "Asmeniniai", "Contacts" : "Kontaktai", "Technical details" : "Techninė informacija", "Remote Address: %s" : "Nuotolinis adresas: %s", "Request ID: %s" : "Užklausos ID: %s" }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"); l10n/zh_CN.json 0000604 00000010056 15247100403 0007204 0 ustar 00 { "translations": { "Calendar" : "日历", "Todos" : "待办事项", "{actor} created calendar {calendar}" : "{actor} 创建了日历 {calendar}", "You created calendar {calendar}" : "您创建的日历 {calendar}", "{actor} deleted calendar {calendar}" : "{actor} 删除了日历 {calendar}", "You deleted calendar {calendar}" : "您删除的日历 {calendar}", "{actor} updated calendar {calendar}" : "{actor} 更新了日历 {calendar}", "You updated calendar {calendar}" : "您更新了日历 {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} 收到的日历分享 {calendar}", "You shared calendar {calendar} with {user}" : "您与 {user} 分享了日历 {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} 与 {user} 分享了日历 {calendar}", "{actor} unshared calendar {calendar} from you" : "{actor} 取消分享 {calendar} 给您", "You unshared calendar {calendar} from {user}" : "您取消分享给 {user} 的日历 {calendar}", "{actor} unshared calendar {calendar} from {user}" : "{actor} 取消分享给 {uesr} 的日历 {calendar}", "{actor} unshared calendar {calendar} from themselves" : "{actor} 取消分享日历 {calendar}", "You shared calendar {calendar} with group {group}" : "您通过组 {group} 分享了日历 {calendar}", "{actor} shared calendar {calendar} with group {group}" : "{actor} 通过组 {group} 分享了日历 {calendar}", "You unshared calendar {calendar} from group {group}" : "您取消分享给组 {group} 的日历 {calendar}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} 取消分享给组 {group} 的日历 {calendar}", "{actor} created event {event} in calendar {calendar}" : "{actor} 在日历 {calendar} 中创建了事件 {event}", "You created event {event} in calendar {calendar}" : "您在日历 {calendar} 中创建了事件 {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} 在日历 {calendar} 中删除了事件 {event}", "You deleted event {event} from calendar {calendar}" : "您在日历 {calendar} 中删除了事件 {event}", "{actor} updated event {event} in calendar {calendar}" : "{actor} 在日历 {calendar} 中更新了事件 {event}", "You updated event {event} in calendar {calendar}" : "您在日历 {calendar} 中更新了事件 {event}", "{actor} created todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中创建了待办事项 {todo}", "You created todo {todo} in list {calendar}" : "您在列表 {calendar} 中创建了待办事项 {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} 在列表 {calendar} 中删除了待办事项 {todo}", "You deleted todo {todo} from list {calendar}" : "您在列表 {calendar} 中删除了待办事项 {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中更新了待办事项 {todo}", "You updated todo {todo} in list {calendar}" : "您在列表 {calendar} 中更新了待办事项 {todo}", "{actor} solved todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中解决了待办事项 {todo}", "You solved todo {todo} in list {calendar}" : "您在列表 {calendar} 中解决了待办事项 {todo}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中重新打开了待办事项 {todo}", "You reopened todo {todo} in list {calendar}" : "您在列表 {calendar} 中重新打开了待办事项 {todo}", "A <strong>calendar</strong> was modified" : "<strong>日历</strong>已经修改", "A calendar <strong>event</strong> was modified" : "日历中<strong>事件</strong>已经修改", "A calendar <strong>todo</strong> was modified" : "列表中<strong>待办事项</strong>已经修改", "Contact birthdays" : "联系人生日", "Personal" : "个人", "Contacts" : "联系人", "Technical details" : "技术细节", "Remote Address: %s" : "远程地址: %s", "Request ID: %s" : "请求 ID: %s" },"pluralForm" :"nplurals=1; plural=0;" } l10n/gl.json 0000604 00000010670 15247100403 0006607 0 ustar 00 { "translations": { "Calendar" : "Calendario", "Todos" : "Asuntos pendentes", "{actor} created calendar {calendar}" : "{actor} creou o calendario {calendar}", "You created calendar {calendar}" : "Vostede creou o calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} eliminou o calendario {calendar}", "You deleted calendar {calendar}" : "Vostede eliminou o calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizou o calendario {calendar}", "You updated calendar {calendar}" : "Vostede actualizou o calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con vostede", "You shared calendar {calendar} with {user}" : "Vostede compartiu o calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartiu o calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} deixou de compartir o calendario {calendar} de vostede", "You unshared calendar {calendar} from {user}" : "Vostede deixou de compartir o calendario {calendar} de {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} deixou de compartir o calendario {calendar} de {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} deixou de compartir o seu propio calendario {calendar}", "You shared calendar {calendar} with group {group}" : "Vostede compartiu o calendario {calendar} co grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartiu o calendario {calendar} co grupo {group}", "You unshared calendar {calendar} from group {group}" : "Vostede deixou de compartir o calendario {calendar} do grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} deixou de compartir o calendario {calendar} do grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creou o evento {event} no calendario {calendar}", "You created event {event} in calendar {calendar}" : "Vostede creou o evento {event} no calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} eliminou o evento {event} do calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Vostede eliminou o evento {event} do calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizou o evento {event} no calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Vostede actualizou o evento {event} no calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creou os asuntos pendentes {todo} na lista {calendar}", "You created todo {todo} in list {calendar}" : "Vostede creou os asuntos pendentes {todo} na lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} eliminou os asuntos pendentes {todo} da lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Vostede eliminou os asuntos pendentes {todo} da lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizou os asuntos pendentes {todo} na lista {calendar}", "You updated todo {todo} in list {calendar}" : "Vostede actualizou os asuntos pendentes {todo} na lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} resolveu os asuntos pendentes {todo} na lista {calendar}", "You solved todo {todo} in list {calendar}" : "Vostede resolveu os asuntos pendentes {todo} na lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} volveu abrir os asuntos pendentes {todo} na lista {calendar}", "You reopened todo {todo} in list {calendar}" : "Vostede volveu abrir os asuntos pendentes {todo} na lista {calendar}", "A <strong>calendar</strong> was modified" : "Foi modificado un <strong>calendario</strong>", "A calendar <strong>event</strong> was modified" : "Foi modificado un <strong>evento</strong> do calendario", "A calendar <strong>todo</strong> was modified" : "Foi modificado un <strong>asunto pendente</strong> do calendario", "Contact birthdays" : "Aniversario do contacto", "Personal" : "Persoal", "Contacts" : "Contactos", "WebDAV" : "WebDAV", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Enderezo remoto: %s", "Request ID: %s" : "ID da solicitude: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/de.js 0000604 00000010676 15247100403 0006246 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Aufgaben", "{actor} created calendar {calendar}" : "{actor} hat den Kalender {calendar} erstellt", "You created calendar {calendar}" : "Du hast den Kalender {calendar} erstellt", "{actor} deleted calendar {calendar}" : "{actor} hat den Kalender {calendar} gelöscht", "You deleted calendar {calendar}" : "Du hast den Kalender {calendar} gelöscht", "{actor} updated calendar {calendar}" : "{actor} hat den Kalender {calendar} aktualisiert", "You updated calendar {calendar}" : "Du hast den Kalender {calendar} aktualisiert", "{actor} shared calendar {calendar} with you" : "{actor} hat den Kalender {calendar} mit Dir geteilt", "You shared calendar {calendar} with {user}" : "Du hast den Kalender {calendar} mit {user} geteilt", "{actor} shared calendar {calendar} with {user}" : "{actor} hat den Kalender {calendar} mit {user} geteilt", "{actor} unshared calendar {calendar} from you" : "{actor} teilt den Kalender {calendar} nicht mehr mit Dir", "You unshared calendar {calendar} from {user}" : "Du teilst den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} teilt den Kalender {calendar} nicht mehr mit {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} teilt den Kalender {calendar} nicht mehr mit sich selbst", "You shared calendar {calendar} with group {group}" : "Du hast den Kalender {calendar} mit der Gruppe {group} geteilt", "{actor} shared calendar {calendar} with group {group}" : "{actor} teilt den Kalender {calendar} mit der Gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Du teilst den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} teilt den Kalender {calendar} nicht mehr mit der Gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} erstellt", "You created event {event} in calendar {calendar}" : "Du hast das Ereignis {event} im Kalender {calendar} erstellt", "{actor} deleted event {event} from calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} gelöscht", "You deleted event {event} from calendar {calendar}" : "Du hast das Ereignis {event} im Kalender {calendar} gelöscht", "{actor} updated event {event} in calendar {calendar}" : "{actor} hat das Ereignis {event} im Kalender {calendar} aktualisiert", "You updated event {event} in calendar {calendar}" : "Du hast das Ereignis {event} im Kalender {calendar} aktualisiert", "{actor} created todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erstellt", "You created todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} erstellt", "{actor} deleted todo {todo} from list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} gelöscht", "You deleted todo {todo} from list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} gelöscht", "{actor} updated todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} aktualisiert", "You updated todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} aktualisiert", "{actor} solved todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} erledigt", "You solved todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} erledigt", "{actor} reopened todo {todo} in list {calendar}" : "{actor} hat die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "You reopened todo {todo} in list {calendar}" : "Du hast die Aufgabe {todo} in der Liste {calendar} wiedereröffnet", "A <strong>calendar</strong> was modified" : "Ein <strong>Kalender</strong> wurde bearbeitet", "A calendar <strong>event</strong> was modified" : "Ein Kalender-<strong>Ereignis</strong> wurde bearbeitet", "A calendar <strong>todo</strong> was modified" : "Eine Kalender-<strong>Aufgabe</strong> wurde bearbeitet", "Contact birthdays" : "Geburtstage von Kontakten", "Personal" : "Persönlich", "Contacts" : "Kontakte", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s" }, "nplurals=2; plural=(n != 1);"); l10n/nb_NO.json 0000604 00000010244 15247100403 0007175 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Gjøremål", "{actor} created calendar {calendar}" : "{actor} opprettet kalenderen {calendar}", "You created calendar {calendar}" : "Du opprettet kalenderen {calendar}", "{actor} deleted calendar {calendar}" : "{actor} slettet kalenderen {calendar}", "You deleted calendar {calendar}" : "Du slettet kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} oppdaterte kalenderen {calendar}", "You updated calendar {calendar}" : "Du oppdaterte kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med deg", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} fjernet delingen av kalenderen {calendar} med deg", "You unshared calendar {calendar} from {user}" : "Du fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} fjernet delingen av kalender {calendar} med seg selv", "You shared calendar {calendar} with group {group}" : "Du delte kalender {calendar} med gruppe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delte kalenderen {calendar} med gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Du fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} opprettet en hendelse {event} i kalenderen {calendar}", "You created event {event} in calendar {calendar}" : "Du opprettet en hendelse {event} i kalenderen {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} slettet hendelsen {event} fra kalenderen {calendar}", "You deleted event {event} from calendar {calendar}" : "Du slettet hendelsen {event} fra kalenderen {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} oppdaterte hendelse {event} i kalenderen {calendar}", "You updated event {event} in calendar {calendar}" : "Du oppdaterte hendelse {event} i kalenderen {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} opprettet en oppgave {todo} i listen {calendar}", "You created todo {todo} in list {calendar}" : "Du opprettet en oppgave {todo} i listen {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} slettet en oppgave {todo} fra listen {calendar}", "You deleted todo {todo} from list {calendar}" : "Du slettet en oppgave {todo} fra listen {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} oppdaterte en oppgave {todo} i listen {calendar}", "You updated todo {todo} in list {calendar}" : "Du oppdaterte en oppgave {todo} i listen {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} ferdigstilte oppgaven {todo} i listen {calendar}", "You solved todo {todo} in list {calendar}" : "Du ferdigstilte oppgaven {todo} i listen {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} gjenåpnet oppgaven {todo} i listen {calendar}", "You reopened todo {todo} in list {calendar}" : "Du gjenåpnet oppgaven {todo} i listen {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> ble endret", "A calendar <strong>event</strong> was modified" : "En kalender <strong>hendelse</strong> ble endret", "A calendar <strong>todo</strong> was modified" : "En kalende <strong>gjøremål</strong> ble endret", "Contact birthdays" : "Kontakters fødelsdag", "Personal" : "Personlig", "Contacts" : "Kontakter", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Ekstern adresse: %s", "Request ID: %s" : "Forespørsel ID: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/es_AR.js 0000604 00000010605 15247100403 0006637 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendario", "Todos" : "Pendientes", "{actor} created calendar {calendar}" : "{actor} creó el calendario {calendar}", "You created calendar {calendar}" : "Usted creó el calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} borró el calendario {calendar}", "You deleted calendar {calendar}" : "Usted borró el calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizó el calendario {calendar}", "You updated calendar {calendar}" : "Usted actualizó el calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} ha compartido el calendario {calendar} con usted", "You shared calendar {calendar} with {user}" : "Usted ha compartido el calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartió el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ha dejado de compartir el calendario {calendar} con usted", "You unshared calendar {calendar} from {user}" : "Usted ha dejado de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} dejó de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} dejó de compartir {el calendario calendar} con él mismo", "You shared calendar {calendar} with group {group}" : "Usted ha compartido el calendario {calendar} con el grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartió el calendario {calendar} con el grupo {group}", "You unshared calendar {calendar} from group {group}" : "Usted ha dejado de compartir el calendario {calendar} con el grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} dejó de compartir el calendrio {calendar} con el grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creó el evento {event} en el calendario {calendar}", "You created event {event} in calendar {calendar}" : "Usted creó el evento {event} en el calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} borró el eventó {event} del calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Usted borró el evento {event} del calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizó el evento {event} en el calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Usted actualizó el evento {event} en el calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creó el pendiente {todo} en la lista {calendar}", "You created todo {todo} in list {calendar}" : "Usted creo el pendiente {todo} en la lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} borró el pendiente {todo} de la lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Usted borró el pendiente {todo} de la lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizó el pendiente {todo} de la lista {calendar}", "You updated todo {todo} in list {calendar}" : "Usted actualizó el pendiente {todo} de la lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} resolvió el pendiente {todo} de la lista {calendar}", "You solved todo {todo} in list {calendar}" : "Usted resolvió el pendiente {todo} de la lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabrió el pendiente {todo} de la lista{calendar}", "You reopened todo {todo} in list {calendar}" : "Usted reabrió el pendiente {todo} de la lista {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> fue modificado", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> de un calendario fue modificado", "A calendar <strong>todo</strong> was modified" : "Un <strong>pendiente</strong> de un calendario fue modificado", "Contact birthdays" : "Cumpleaños del contacto", "Personal" : "Personal", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s" }, "nplurals=2; plural=(n != 1);"); l10n/cs.js 0000604 00000010520 15247100403 0006247 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalendář", "Todos" : "Úkoly", "{actor} created calendar {calendar}" : "{actor} vytvořil(a) kalendář {calendar}", "You created calendar {calendar}" : "Vytvořil(a", "{actor} deleted calendar {calendar}" : "{actor} smazal(a) kalendář {calendar}", "You deleted calendar {calendar}" : "Smazal(a) jste kalendář {calendar}", "{actor} updated calendar {calendar}" : "{actor} aktualizoval(a) kalendář {calendar}", "You updated calendar {calendar}" : "Aktualizoval(a) jste kalendář {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} s vámi nasdílel(a) kalendář {calendar}", "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} začal sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from you" : "{actor} s vámi přestal(a) sdílet kalendář {calendar}", "You unshared calendar {calendar} from {user}" : "S uživatelem {user} jste přestal(a) sdílet kalendář {calendar}", "{actor} unshared calendar {calendar} from {user}" : "{actor} přestal(a) sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} přestal sdílet kalendář {calendar} sám se sebou", "You shared calendar {calendar} with group {group}" : "Se skupinou {group} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with group {group}" : "{actor} nasdílel(a) kalendář {calendar} skupině {group}", "You unshared calendar {calendar} from group {group}" : "Zrušil(a) jste sdílení kalendáře {calendar} skupině {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} přestal(a) sdílet kalendář {calendar} se skupinou {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} vytvořil(a) událost {event} v kalendáři {calendar}", "You created event {event} in calendar {calendar}" : "V kalendáři {calendar} jste vytvořil(a) událost {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} z kalendáře {calendar} smazal(a) událost {event}", "You deleted event {event} from calendar {calendar}" : "Smazal(a) jste událost {event} z kalendáře {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} aktualizoval(a) událost {event} v kalendáři {calendar}", "You updated event {event} in calendar {calendar}" : "Aktualizoval(a) jste událost {event} v kalendáři {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} vytvořil(a) v seznamu {calendar} vytvořila úkol {todo}", "You created todo {todo} in list {calendar}" : "V seznamu {calendar} jste vytvořil(a) úkol {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} smazal(a) úkol {todo} ze seznamu {calendar}", "You deleted todo {todo} from list {calendar}" : "Ze seznamu {todo} jste smazal(a) úkol {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} aktualizoval(a) úkol {todo} v seznamu {calendar}", "You updated todo {todo} in list {calendar}" : "Aktualizoval(a) jste úkol {todo} v seznamu {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} vyřešil(a) úkol {todo} v seznamu {calendar}", "You solved todo {todo} in list {calendar}" : "Vyřešil(a) jste úkol {todo} v seznamu {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} znovu otevřel(a) úkol {todo} v seznamu {calendar}", "You reopened todo {todo} in list {calendar}" : "Znovu jste otevřel(a) úkol {todo} v seznamu {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalendář</strong> byl změněn", "A calendar <strong>event</strong> was modified" : "<strong>Událost</strong> v kalendáři byla změněna", "A calendar <strong>todo</strong> was modified" : "<strong>Úkol</strong> v kalendáři byl změněn", "Contact birthdays" : "Narozeniny kontaktů", "Personal" : "Osobní", "Contacts" : "Kontakty", "Technical details" : "Technické detaily", "Remote Address: %s" : "Vzdálená adresa: %s", "Request ID: %s" : "ID požadavku: %s" }, "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"); l10n/nl.js 0000604 00000010112 15247100403 0006250 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Te doen", "{actor} created calendar {calendar}" : "{actor} creëerde agenda {calendar}", "You created calendar {calendar}" : "Jij creëerde agenda {calendar}", "{actor} deleted calendar {calendar}" : "{actor} verwijderde agenda {calendar}", "You deleted calendar {calendar}" : "Je verwijderde agenda {calendar}", "{actor} updated calendar {calendar}" : "{actor} werkte agenda {calendar} bij", "You updated calendar {calendar}" : "Je werkte agenda {calendar} bij", "{actor} shared calendar {calendar} with you" : "{actor} deelde agenda {calendar} met jou", "You shared calendar {calendar} with {user}" : "Je deelde agenda {calendar} met {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} deelde agenda {calendar} met {user}", "{actor} unshared calendar {calendar} from you" : "{actor} stopte het delen van agenda {calendar} met jou", "You unshared calendar {calendar} from {user}" : "Je stopte met delen agenda {calendar} met {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} stopte met delen agenda {calendar} met {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} stopte het delen van agenda {calendar} met zichzelf", "You shared calendar {calendar} with group {group}" : "Je deelde agenda {calendar} met groep {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} deelde agenda {calendar} met groep {group}", "You unshared calendar {calendar} from group {group}" : "Je stopte het delen van agenda {calendar} met groep {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} stopte het delen van agenda {calendar} met groep {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creëerde afspraak {event} in agenda {calendar}", "You created event {event} in calendar {calendar}" : "Je creëerde afspraak {event} in agenda {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} verwijderde afspraak {event} uit agenda {calendar}", "You deleted event {event} from calendar {calendar}" : "Je verwijderde afspraak {event} uit agenda {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} heeft afspraak {event} in agenda {calendar} bijgewerkt", "You updated event {event} in calendar {calendar}" : "Je hebt afspraak {event} in agenda {calendar} bijgewerkt", "{actor} created todo {todo} in list {calendar}" : "{actor} creëerde taak {todo} in lijst {calendar}", "You created todo {todo} in list {calendar}" : "Jij creëerde taak {todo} in lijst {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} verwijderde taak {todo} uit lijst {calendar}", "You deleted todo {todo} from list {calendar}" : "Je verwijderde taak {todo} uit lijst {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} heeft taak {todo} bijgewerkt in lijst {calendar}", "You updated todo {todo} in list {calendar}" : "Je hebt taak {todo} bijgewerkt in lijst {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} heeft taak {todo} in lijst {calendar} afgewerkt", "You solved todo {todo} in list {calendar}" : "Je hebt taak {todo} in lijst {calendar} afgewerkt", "{actor} reopened todo {todo} in list {calendar}" : "{actor} heropende taak {todo} in lijst {calendar}", "You reopened todo {todo} in list {calendar}" : "Je heropende taak {todo} in lijst {calendar}", "A <strong>calendar</strong> was modified" : "Een <strong>agenda</strong> is aangepast", "A calendar <strong>event</strong> was modified" : "Een agenda <strong>gebeurtenis</strong> is aangepast", "A calendar <strong>todo</strong> was modified" : "Een agenda <strong>Te doen</strong> was aangepast", "Contact birthdays" : "Verjaardagen", "Personal" : "Persoonlijk", "Contacts" : "Contactpersonen", "Technical details" : "Technische details", "Remote Address: %s" : "Extern adres: %s", "Request ID: %s" : "Aanvraag-ID: %s" }, "nplurals=2; plural=(n != 1);"); l10n/sl.json 0000604 00000000560 15247100403 0006620 0 ustar 00 { "translations": { "Contact birthdays" : "Rojstni dnevi kontaktov", "Personal" : "Osebno", "Contacts" : "Stiki", "Technical details" : "Tehnične podrobnosti", "Remote Address: %s" : "Oddaljeni naslov: %s", "Request ID: %s" : "ID Zahteve: %s" },"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" } l10n/sq.json 0000604 00000010175 15247100403 0006630 0 ustar 00 { "translations": { "Calendar" : "Kalendar", "Todos" : "Për tu bërë", "{actor} created calendar {calendar}" : "{aktori} krijoi kalendarin {kalendarin}", "You created calendar {calendar}" : "Ju krijuat kalendarin {calendar}", "{actor} deleted calendar {calendar}" : "{actor} fshiu kalendarin {calendar}", "You deleted calendar {calendar}" : "Ju fshit kalendarin {calendar}", "{actor} updated calendar {calendar}" : "{actor} përditësoi kalendarin {calendar}", "You updated calendar {calendar}" : "Ju përditësuat kalendarin {calendar}", "{actor} shared calendar {calendar} with you" : " {actor} ndau kalendarin {calendar} me ju", "You shared calendar {calendar} with {user}" : "Ju ndat kalendarin {calendar} me {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} ndau kalendarin {calendar} me {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ndaloj së ndari kalendarin {calendar} me ju", "You unshared calendar {calendar} from {user}" : "Ju ndaluat së ndari kalendarin {calendar} me {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} ndaloj së ndari kalendarin {calendar} me {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} ndaloj së ndari kalendarin {calendar} me veten", "You shared calendar {calendar} with group {group}" : "Ju ndat kalendarin {calendar} me grupin {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} ndau kalendarin {calendar} me grupin {group}", "You unshared calendar {calendar} from group {group}" : "Ju ndaluat së ndari kalendarin {calendar} me grupin {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} ndaloj së ndari kalendarin {calendar} me grupin {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} krijoj eventin {event} në kalendarin {calendar}", "You created event {event} in calendar {calendar}" : "Ju krijuat eventin {event} në kalendarin {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} fshiu eventin {event} nga kalendari {calendar}", "You deleted event {event} from calendar {calendar}" : "Ju fshit eventin {event} nga kalndari {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} përditsoi eventin {event} në kalndarin {calendar}", "You updated event {event} in calendar {calendar}" : "Ju përditësuat eventin {event} në kalndarin {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} u krijua todo{todo} në listën {calendar}", "You created todo {todo} in list {calendar}" : "Ju krijuat todo {todo} në listën {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} u fshi todo{ todo} nga lista{calendar}", "You deleted todo {todo} from list {calendar}" : "Ju fshit todo{todo} nga lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} u përditësua todo{todo} në listën{calendar}", "You updated todo {todo} in list {calendar}" : "Ju përditësuat përtëbërë {todo} në listën{calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} zgjidhi përtëbërë {todo} në listën {calendar}", "You solved todo {todo} in list {calendar}" : "Ju zgjidhët përtëbërë {todo} në listën {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} rihapi përtëbërë {todo} në listën {calendar}", "You reopened todo {todo} in list {calendar}" : "Ju rihapët përtëbërë {todo} në listën {calendar}", "A <strong>calendar</strong> was modified" : "Një <strong>kalendar</strong> u modifikua", "A calendar <strong>event</strong> was modified" : "Një <strong>event</strong> në kalendar u modifikua", "A calendar <strong>todo</strong> was modified" : "Një kalendar <strong>todo<strong> u modifikua", "Contact birthdays" : "Ditëlindjet e kontakteve", "Personal" : "Personale", "Contacts" : "Kontaktet", "Technical details" : "Detaje teknike", "Remote Address: %s" : "Adresa remote: %s", "Request ID: %s" : "ID e kërkesës: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/gl.js 0000604 00000010673 15247100403 0006255 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendario", "Todos" : "Asuntos pendentes", "{actor} created calendar {calendar}" : "{actor} creou o calendario {calendar}", "You created calendar {calendar}" : "Vostede creou o calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} eliminou o calendario {calendar}", "You deleted calendar {calendar}" : "Vostede eliminou o calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizou o calendario {calendar}", "You updated calendar {calendar}" : "Vostede actualizou o calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con vostede", "You shared calendar {calendar} with {user}" : "Vostede compartiu o calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartiu o calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} deixou de compartir o calendario {calendar} de vostede", "You unshared calendar {calendar} from {user}" : "Vostede deixou de compartir o calendario {calendar} de {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} deixou de compartir o calendario {calendar} de {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} deixou de compartir o seu propio calendario {calendar}", "You shared calendar {calendar} with group {group}" : "Vostede compartiu o calendario {calendar} co grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartiu o calendario {calendar} co grupo {group}", "You unshared calendar {calendar} from group {group}" : "Vostede deixou de compartir o calendario {calendar} do grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} deixou de compartir o calendario {calendar} do grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creou o evento {event} no calendario {calendar}", "You created event {event} in calendar {calendar}" : "Vostede creou o evento {event} no calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} eliminou o evento {event} do calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Vostede eliminou o evento {event} do calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizou o evento {event} no calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Vostede actualizou o evento {event} no calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creou os asuntos pendentes {todo} na lista {calendar}", "You created todo {todo} in list {calendar}" : "Vostede creou os asuntos pendentes {todo} na lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} eliminou os asuntos pendentes {todo} da lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Vostede eliminou os asuntos pendentes {todo} da lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizou os asuntos pendentes {todo} na lista {calendar}", "You updated todo {todo} in list {calendar}" : "Vostede actualizou os asuntos pendentes {todo} na lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} resolveu os asuntos pendentes {todo} na lista {calendar}", "You solved todo {todo} in list {calendar}" : "Vostede resolveu os asuntos pendentes {todo} na lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} volveu abrir os asuntos pendentes {todo} na lista {calendar}", "You reopened todo {todo} in list {calendar}" : "Vostede volveu abrir os asuntos pendentes {todo} na lista {calendar}", "A <strong>calendar</strong> was modified" : "Foi modificado un <strong>calendario</strong>", "A calendar <strong>event</strong> was modified" : "Foi modificado un <strong>evento</strong> do calendario", "A calendar <strong>todo</strong> was modified" : "Foi modificado un <strong>asunto pendente</strong> do calendario", "Contact birthdays" : "Aniversario do contacto", "Personal" : "Persoal", "Contacts" : "Contactos", "WebDAV" : "WebDAV", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Enderezo remoto: %s", "Request ID: %s" : "ID da solicitude: %s" }, "nplurals=2; plural=(n != 1);"); l10n/is.js 0000604 00000010472 15247100403 0006263 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Dagatal", "Todos" : "Verkþættir", "{actor} created calendar {calendar}" : "{actor} bjó til dagatalið {calendar}", "You created calendar {calendar}" : "Þú bjóst til dagatalið {calendar}", "{actor} deleted calendar {calendar}" : "{actor} eyddi dagatalinu {calendar}", "You deleted calendar {calendar}" : "Þú eyddir dagatalinu {calendar}", "{actor} updated calendar {calendar}" : "{actor} uppfærði dagatalið {calendar}", "You updated calendar {calendar}" : "Þú uppfærðir dagatalið {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} deildi dagatalinu {calendar} með þér", "You shared calendar {calendar} with {user}" : "Þú deildir dagatalinu {calendar} með {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} deildi dagatalinu {calendar} með {user}", "{actor} unshared calendar {calendar} from you" : "{actor} tók af deilingu á dagatalinu {calendar} frá þér", "You unshared calendar {calendar} from {user}" : "Þú tókst af deilingu á dagatalinu {calendar} frá {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} tók af deilingu á dagatalinu {calendar} frá {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} tók af deilingu á dagatalinu {calendar} frá þeim sjálfum", "You shared calendar {calendar} with group {group}" : "Þú deildir dagatalinu {calendar} með hópnum {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} deildi dagatalinu {calendar} með hópnum {group}", "You unshared calendar {calendar} from group {group}" : "Þú tókst af deilingu á dagatalinu {calendar} frá hópnum {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} tók af deilingu á dagatalinu {calendar} frá hópnum {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} bjó til atburðinn {event} í dagatalinu {calendar}", "You created event {event} in calendar {calendar}" : "Þú bjóst til atburðinn {event} í dagatalinu {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} eyddi atburðinum {event} úr dagatalinu {calendar}", "You deleted event {event} from calendar {calendar}" : "Þú eyddir atburðinum {event} úr dagatalinu {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} uppfærði atburðinn {event} í dagatalinu {calendar}", "You updated event {event} in calendar {calendar}" : "Þú uppfærðir atburðinn {event} í dagatalinu {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} bjó til verkefnið {todo} á listanum {calendar}", "You created todo {todo} in list {calendar}" : "Þú bjóst til verkefnið {todo} á listanum {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} eyddi verkefninu {todo} af listanum {calendar}", "You deleted todo {todo} from list {calendar}" : "Þú eyddir verkefninu {todo} af listanum {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} uppfærði verkefnið {todo} á listanum {calendar}", "You updated todo {todo} in list {calendar}" : "Þú uppfærðir verkefnið {todo} á listanum {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} leysti verkefnið {todo} á listanum {calendar}", "You solved todo {todo} in list {calendar}" : "Þú leystir verkefnið {todo} á listanum {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} opnaði aftur verkefnið {todo} á listanum {calendar}", "You reopened todo {todo} in list {calendar}" : "Þú opnaðir aftur verkefnið {todo} á listanum {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Dagatali</strong> var breytt", "A calendar <strong>event</strong> was modified" : "<strong>Atburði</strong> dagatals var breytt", "A calendar <strong>todo</strong> was modified" : "<strong>Verkefnalista</strong> dagatals var breytt", "Contact birthdays" : "Afmælisdagar tengiliðar", "Personal" : "Einka", "Contacts" : "Tengiliðir", "Technical details" : "Tæknilegar upplýsingar", "Remote Address: %s" : "Fjartengt vistfang: %s", "Request ID: %s" : "Beiðni um auðkenni: %s" }, "nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);"); l10n/hu.js 0000604 00000011120 15247100403 0006253 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Naptár", "Todos" : "Teendők", "{actor} created calendar {calendar}" : "{actor} létrehozta a naptárt: {calendar}", "You created calendar {calendar}" : "Létrehoztad a naptárt: {calendar}", "{actor} deleted calendar {calendar}" : "{actor} törölte a naptárt: {calendar}", "You deleted calendar {calendar}" : "Törölted a naptárt: {calendar}", "{actor} updated calendar {calendar}" : "{actor} frissítette a napárt: {calendar}", "You updated calendar {calendar}" : "Frissítetted a naptárt: {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} megosztotta veled ezt a naptárt: {calendar}", "You shared calendar {calendar} with {user}" : "Megosztottad ezt a napárt: {calendar} vele: {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} megosztotta ezt a napárt: {calendar} vele: {user}", "{actor} unshared calendar {calendar} from you" : "{actor} visszavonta töled a naptár megosztását: {calendar}", "You unshared calendar {calendar} from {user}" : "Visszavontad a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} visszavonta a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} visszavonta tőlük a naptár megosztását: {calendar}", "You shared calendar {calendar} with group {group}" : "Megosztottad ezt a naptárt: {calendar} evvel a csoporttal: {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} megosztotta ezt a naptárt: {calendar} evvel a csoporttal: {group}", "You unshared calendar {calendar} from group {group}" : "Visszavontad ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} visszavonta ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} létrehozta ezt az eseményt: {event} ebben a naptárban: {calendar}", "You created event {event} in calendar {calendar}" : "Létrehoztad ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} törölte ezt az eseményt: {event} ebből a naptárból: {calendar}", "You deleted event {event} from calendar {calendar}" : "Törölted ezt az eseményt: {event} ebből a naptárból: {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} frissítette ezt az eseményt: {event} ebben a naptárban: {calendar}", "You updated event {event} in calendar {calendar}" : "Frissítetted ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} létrehozta ezt a teendőt: {todo} ebben a listában: {calendar}", "You created todo {todo} in list {calendar}" : "Létrehoztad ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} törölte ezt a teendőt: {todo} ebből a listából: {calendar}", "You deleted todo {todo} from list {calendar}" : "Törölted ezt a teendőt: {todo} ebből a listából: {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} frissítette ezt a teendőt: {todo} ebben a listában: {calendar}", "You updated todo {todo} in list {calendar}" : "Frissítetted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} elintézte ezt a teendőt: {todo} ebben a listában: {calendar}", "You solved todo {todo} in list {calendar}" : "Elintézted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} újranyitotta ezt a teendőt: {todo} ebben a listában: {calendar}", "You reopened todo {todo} in list {calendar}" : "Újranyitottad ezt a teendőt: {todo} ebben a listában: {calendar}", "A <strong>calendar</strong> was modified" : "Egy <strong>naptár</strong> megváltozott", "A calendar <strong>event</strong> was modified" : "Egy naptár <strong>esemény</strong> megváltozott", "A calendar <strong>todo</strong> was modified" : "Egy naptár <strong>teendő</strong> megváltozott", "Contact birthdays" : "Születésnapok", "Personal" : "Személyes", "Contacts" : "Névjegyek", "Technical details" : "Technikai adatok", "Remote Address: %s" : "Távoli cím: %s", "Request ID: %s" : "Kérelem azonosító: %s" }, "nplurals=2; plural=(n != 1);"); l10n/lt_LT.json 0000604 00000010605 15247100403 0007221 0 ustar 00 { "translations": { "Calendar" : "Kalendorius", "Todos" : "Užduotys", "{actor} created calendar {calendar}" : "{actor} sukūrė kalendorių {calendar}", "You created calendar {calendar}" : "Jūs sukūrėte kalendorių {calendar}", "{actor} deleted calendar {calendar}" : "{actor} ištrynė kalendorių {calendar}", "You deleted calendar {calendar}" : "Jūs ištrynėte kalendorių {calendar}", "{actor} updated calendar {calendar}" : "{actor} atnaujino kalendorių {calendar}", "You updated calendar {calendar}" : "Jūs atnaujinote kalendorių {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} pradėjo bendrinti su jumis kalendorių {calendar}", "You shared calendar {calendar} with {user}" : "Jūs pradėjote bendrinti kalendorių {calendar} su {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} pradėjo bendrinti kalendorių {calendar} su {user}", "{actor} unshared calendar {calendar} from you" : "{actor} nustojo bendrinti su jumis kalendorių {calendar}", "You unshared calendar {calendar} from {user}" : "Jūs nustojote bendrinti kalendorių {calendar} su {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} nustojo bendrinti kalendorių {calendar} su {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} nustojo bendrinti su savimi kalendorių {calendar}", "You shared calendar {calendar} with group {group}" : "Jūs pradėjote bendrinti kalendorių {calendar} su grupe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} pradėjo bendrinti kalendorių {calendar} su grupe {group}", "You unshared calendar {calendar} from group {group}" : "Jūs nustojote bendrinti kalendorių {calendar} su grupe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} nustojo bendrinti kalendorių {calendar} su grupe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} kalendoriuje {calendar} sukūrė įvykį {event}", "You created event {event} in calendar {calendar}" : "Jūs kalendoriuje {calendar} sukūrėte įvykį {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} ištrynė įvykį {event} iš kalendoriaus {calendar}", "You deleted event {event} from calendar {calendar}" : "Jūs ištrynėte įvykį {event} iš kalendoriaus {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} kalendoriuje {calendar} atnaujino įvykį {event}", "You updated event {event} in calendar {calendar}" : "Jūs kalendoriuje {calendar} atnaujinote įvykį {event}", "{actor} created todo {todo} in list {calendar}" : "{actor} sąraše {calendar} sukūrė užduotį {todo}", "You created todo {todo} in list {calendar}" : "Jūs sąraše {calendar} sukūrėte užduotį {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} iš sąrašo {calendar} ištrynė užduotį {todo}", "You deleted todo {todo} from list {calendar}" : "Jūs iš sąrašo {calendar} ištrynėte užduotį {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} sąraše {calendar} atnaujino užduotį {todo}", "You updated todo {todo} in list {calendar}" : "Jūs sąraše {calendar} atnaujinote užduotį {todo}", "{actor} solved todo {todo} in list {calendar}" : "{actor} sąraše {calendar} išsprendė užduotį {todo}", "You solved todo {todo} in list {calendar}" : "Jūs sąraše {calendar} išsprendėte užduotį {todo}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} sąraše {calendar} vėl pradėjo užduotį {todo}", "You reopened todo {todo} in list {calendar}" : "Jūs sąraše {calendar} vėl pradėjote užduotį {todo}", "A <strong>calendar</strong> was modified" : "<strong>Kalendorius</strong> buvo pakeistas", "A calendar <strong>event</strong> was modified" : "Kalendoriaus <strong>įvykis</strong> buvo pakeistas", "A calendar <strong>todo</strong> was modified" : "Kalendoriaus <strong>užduotis</strong> buvo pakeista", "Contact birthdays" : "Kontaktų gimtadieniai", "Personal" : "Asmeniniai", "Contacts" : "Kontaktai", "Technical details" : "Techninė informacija", "Remote Address: %s" : "Nuotolinis adresas: %s", "Request ID: %s" : "Užklausos ID: %s" },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" } l10n/id.js 0000604 00000000456 15247100403 0006245 0 ustar 00 OC.L10N.register( "dav", { "Contact birthdays" : "Ulang tahun kontak", "Personal" : "Pribadi", "Contacts" : "Kontak", "Technical details" : "Rincian teknis", "Remote Address: %s" : "Alamat remote: %s", "Request ID: %s" : "ID Permintaan: %s" }, "nplurals=1; plural=0;"); l10n/sv.js 0000604 00000010071 15247100403 0006273 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Uppgifter", "{actor} created calendar {calendar}" : "{actor} skapade kalender {calendar}", "You created calendar {calendar}" : "Du skapade kalender {calendar}", "{actor} deleted calendar {calendar}" : "{actor} raderade kalender {calendar}", "You deleted calendar {calendar}" : "Du raderade kalender {calendar}", "{actor} updated calendar {calendar}" : "{actor} uppdaterade kalender {calendar}", "You updated calendar {calendar}" : "Du uppdaterade kalender {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delade kalender {calendar} med dig", "You shared calendar {calendar} with {user}" : "Du delade kalender {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delade kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} slutade dela kalender {calendar} med dig", "You unshared calendar {calendar} from {user}" : "Du slutade dela kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} slutade dela kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} slutade dela kalender {calendar} med sig själv", "You shared calendar {calendar} with group {group}" : "Du delade kalender {calendar} med grupp {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delade kalender {calendar} med grupp {group}", "You unshared calendar {calendar} from group {group}" : "Du slutade dela kalender {calendar} med gruppen {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} slutade dela kalender {calendar} med gruppen {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} skapade händelse {event} i kalender {calendar}", "You created event {event} in calendar {calendar}" : "Du skapade händelse {event} i kalender {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} raderade händelse {event} från kalender {calendar}", "You deleted event {event} from calendar {calendar}" : "Du raderade händelse {event} från kalender {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} uppdaterade händelse {event} i kalender {calendar}", "You updated event {event} in calendar {calendar}" : "Du uppdaterade händelse {event} i kalender {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} skapade uppgift {todo} i listan {calendar}", "You created todo {todo} in list {calendar}" : "Du skapade uppgift {todo} i listan {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} raderade uppgift {todo} från listan {calendar}", "You deleted todo {todo} from list {calendar}" : "Du raderade uppgift {todo} från listan {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} uppdaterade uppgift {todo} i listan {calendar}", "You updated todo {todo} in list {calendar}" : "Du uppdaterade uppgift {todo} i listan {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} löste uppgift {todo} i listan {calendar}", "You solved todo {todo} in list {calendar}" : "Du löste uppgift {todo} i listan {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} återupptog uppgift {todo} i listan {calendar}", "You reopened todo {todo} in list {calendar}" : "Du återupptog uppgift {todo} i listan {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> modifierades", "A calendar <strong>event</strong> was modified" : "En kalender-<strong>händelse</strong> modifierades", "A calendar <strong>todo</strong> was modified" : "En kalender <strong>uppgift</strong> modifierades", "Contact birthdays" : "Födelsedagar", "Personal" : "Privat", "Contacts" : "Kontakter", "Technical details" : "Tekniska detaljer", "Remote Address: %s" : "Extern adress: %s", "Request ID: %s" : "Begär ID: %s" }, "nplurals=2; plural=(n != 1);"); l10n/cs_CZ.json 0000604 00000010515 15247100403 0007204 0 ustar 00 { "translations": { "Calendar" : "Kalendář", "Todos" : "Úkoly", "{actor} created calendar {calendar}" : "{actor} vytvořil(a) kalendář {calendar}", "You created calendar {calendar}" : "Vytvořil(a", "{actor} deleted calendar {calendar}" : "{actor} smazal(a) kalendář {calendar}", "You deleted calendar {calendar}" : "Smazal(a) jste kalendář {calendar}", "{actor} updated calendar {calendar}" : "{actor} aktualizoval(a) kalendář {calendar}", "You updated calendar {calendar}" : "Aktualizoval(a) jste kalendář {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} s vámi nasdílel(a) kalendář {calendar}", "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} začal sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from you" : "{actor} s vámi přestal(a) sdílet kalendář {calendar}", "You unshared calendar {calendar} from {user}" : "S uživatelem {user} jste přestal(a) sdílet kalendář {calendar}", "{actor} unshared calendar {calendar} from {user}" : "{actor} přestal(a) sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} přestal sdílet kalendář {calendar} sám se sebou", "You shared calendar {calendar} with group {group}" : "Se skupinou {group} jste začal(a) sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with group {group}" : "{actor} nasdílel(a) kalendář {calendar} skupině {group}", "You unshared calendar {calendar} from group {group}" : "Zrušil(a) jste sdílení kalendáře {calendar} skupině {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} přestal(a) sdílet kalendář {calendar} se skupinou {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} vytvořil(a) událost {event} v kalendáři {calendar}", "You created event {event} in calendar {calendar}" : "V kalendáři {calendar} jste vytvořil(a) událost {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} z kalendáře {calendar} smazal(a) událost {event}", "You deleted event {event} from calendar {calendar}" : "Smazal(a) jste událost {event} z kalendáře {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} aktualizoval(a) událost {event} v kalendáři {calendar}", "You updated event {event} in calendar {calendar}" : "Aktualizoval(a) jste událost {event} v kalendáři {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} vytvořil(a) v seznamu {calendar} vytvořila úkol {todo}", "You created todo {todo} in list {calendar}" : "V seznamu {calendar} jste vytvořil(a) úkol {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} smazal(a) úkol {todo} ze seznamu {calendar}", "You deleted todo {todo} from list {calendar}" : "Ze seznamu {todo} jste smazal(a) úkol {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} aktualizoval(a) úkol {todo} v seznamu {calendar}", "You updated todo {todo} in list {calendar}" : "Aktualizoval(a) jste úkol {todo} v seznamu {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} vyřešil(a) úkol {todo} v seznamu {calendar}", "You solved todo {todo} in list {calendar}" : "Vyřešil(a) jste úkol {todo} v seznamu {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} znovu otevřel(a) úkol {todo} v seznamu {calendar}", "You reopened todo {todo} in list {calendar}" : "Znovu jste otevřel(a) úkol {todo} v seznamu {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalendář</strong> byl změněn", "A calendar <strong>event</strong> was modified" : "<strong>Událost</strong> v kalendáři byla změněna", "A calendar <strong>todo</strong> was modified" : "<strong>Úkol</strong> v kalendáři byl změněn", "Contact birthdays" : "Narozeniny kontaktů", "Personal" : "Osobní", "Contacts" : "Kontakty", "Technical details" : "Technické detaily", "Remote Address: %s" : "Vzdálená adresa: %s", "Request ID: %s" : "ID požadavku: %s" },"pluralForm" :"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" } l10n/pt_BR.json 0000604 00000010373 15247100403 0007213 0 ustar 00 { "translations": { "Calendar" : "Calendário", "Todos" : "Tarefas", "{actor} created calendar {calendar}" : "{actor} criou o calendário {calendar}", "You created calendar {calendar}" : "Você criou o calendário {calendar}", "{actor} deleted calendar {calendar}" : "{actor} excluiu o calendário {calendar}", "You deleted calendar {calendar}" : "Você excluiu o calendário {calendar}", "{actor} updated calendar {calendar}" : "{actor} atualizou o calendário {calendar}", "You updated calendar {calendar}" : "Você atualizou o calendário {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} compartilhou o calendário {calendar} com você", "You shared calendar {calendar} with {user}" : "Você compartilhou o calendário {calendar} com {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartilhou o calendário {calendar} com {user}", "{actor} unshared calendar {calendar} from you" : "{actor} descompartilhou o calendário {calendar} com você", "You unshared calendar {calendar} from {user}" : "Você descompartilhou o calendário {calendar} com {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} descompartilhou o calendário {calendar} de {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} descompartilhou o calendário {calendar} deles mesmos", "You shared calendar {calendar} with group {group}" : "Você compartilhou o calendário {calendar} com o grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartilhou o calendário {calendar} com o grupo {group}", "You unshared calendar {calendar} from group {group}" : "Você descompartilhou o calendário {calendar} do grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} descompartilhou o calendário {calendar} do grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} criou o evento {event} no calendário {calendar}", "You created event {event} in calendar {calendar}" : "Você criou o evento {event} no calendário {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} excluiu o evento {event} do calendário {calendar}", "You deleted event {event} from calendar {calendar}" : "Você excluiu o evento {event} do calendário {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} atualizou o evento {event} no calendário {calendar}", "You updated event {event} in calendar {calendar}" : "Você atualizou o evento {event} no calendário {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} criou a tarefa {todo} na lista {calendar}", "You created todo {todo} in list {calendar}" : "Você criou a tarefa {todo} na lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} excluiu a tarefa {todo} da lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Você excluiu a tarefa {todo} da lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} atualizou a tarefa {todo} na lista {calendar}", "You updated todo {todo} in list {calendar}" : "Você atualizou a tarefa {todo} na lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} tarefa resolvida {todo} na lista {calendar}", "You solved todo {todo} in list {calendar}" : "Você terminou a tarefa {todo} na lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabriu tarefa {todo} na lista {calendar}", "You reopened todo {todo} in list {calendar}" : "Você reabriu a terefa {todo} na lista {calendar}", "A <strong>calendar</strong> was modified" : "Um <strong>calendário</strong> foi modificado", "A calendar <strong>event</strong> was modified" : "Um <strong>evento</strong> do calendário foi modificado", "A calendar <strong>todo</strong> was modified" : "Uma <strong>tarefa</strong> do calendário foi modificada", "Contact birthdays" : "Aniversário dos contatos", "Personal" : "Pessoal", "Contacts" : "Contatos", "Technical details" : "Detalhes técnicos", "Remote Address: %s" : "Endereço remoto: %s", "Request ID: %s" : "ID do solicitante: %s" },"pluralForm" :"nplurals=2; plural=(n > 1);" } l10n/es_AR.json 0000604 00000010602 15247100403 0007171 0 ustar 00 { "translations": { "Calendar" : "Calendario", "Todos" : "Pendientes", "{actor} created calendar {calendar}" : "{actor} creó el calendario {calendar}", "You created calendar {calendar}" : "Usted creó el calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} borró el calendario {calendar}", "You deleted calendar {calendar}" : "Usted borró el calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizó el calendario {calendar}", "You updated calendar {calendar}" : "Usted actualizó el calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} ha compartido el calendario {calendar} con usted", "You shared calendar {calendar} with {user}" : "Usted ha compartido el calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartió el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ha dejado de compartir el calendario {calendar} con usted", "You unshared calendar {calendar} from {user}" : "Usted ha dejado de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} dejó de compartir el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} dejó de compartir {el calendario calendar} con él mismo", "You shared calendar {calendar} with group {group}" : "Usted ha compartido el calendario {calendar} con el grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartió el calendario {calendar} con el grupo {group}", "You unshared calendar {calendar} from group {group}" : "Usted ha dejado de compartir el calendario {calendar} con el grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} dejó de compartir el calendrio {calendar} con el grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creó el evento {event} en el calendario {calendar}", "You created event {event} in calendar {calendar}" : "Usted creó el evento {event} en el calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} borró el eventó {event} del calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Usted borró el evento {event} del calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizó el evento {event} en el calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Usted actualizó el evento {event} en el calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creó el pendiente {todo} en la lista {calendar}", "You created todo {todo} in list {calendar}" : "Usted creo el pendiente {todo} en la lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} borró el pendiente {todo} de la lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Usted borró el pendiente {todo} de la lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizó el pendiente {todo} de la lista {calendar}", "You updated todo {todo} in list {calendar}" : "Usted actualizó el pendiente {todo} de la lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} resolvió el pendiente {todo} de la lista {calendar}", "You solved todo {todo} in list {calendar}" : "Usted resolvió el pendiente {todo} de la lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabrió el pendiente {todo} de la lista{calendar}", "You reopened todo {todo} in list {calendar}" : "Usted reabrió el pendiente {todo} de la lista {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> fue modificado", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> de un calendario fue modificado", "A calendar <strong>todo</strong> was modified" : "Un <strong>pendiente</strong> de un calendario fue modificado", "Contact birthdays" : "Cumpleaños del contacto", "Personal" : "Personal", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/fi_FI.json 0000604 00000000531 15247100403 0007154 0 ustar 00 { "translations": { "Contact birthdays" : "Yhteystietojen syntymäpäivät", "Personal" : "Henkilökohtainen", "Contacts" : "Yhteystiedot", "Technical details" : "Tekniset yksityiskohdat", "Remote Address: %s" : "Etäosoite: %s", "Request ID: %s" : "Pyynnön tunniste: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/sk.json 0000604 00000010151 15247100403 0006614 0 ustar 00 { "translations": { "Calendar" : "Kalendár", "Todos" : "Úlohy", "{actor} created calendar {calendar}" : "[actor] vytvoril kalendár [calendar]", "You created calendar {calendar}" : "Vytvorili ste kalendár [calendar]", "{actor} deleted calendar {calendar}" : "[actor] zmazal kalendár [calendar]", "You deleted calendar {calendar}" : "Zmazali ste kalendár [calendar]", "{actor} updated calendar {calendar}" : "[actor] upravil kalendár [calendar]", "You updated calendar {calendar}" : "Upravili ste kalendár [calendar]", "{actor} shared calendar {calendar} with you" : "{actor} vám sprístupnil kalendár {calendar}", "You shared calendar {calendar} with {user}" : "Sprístupnili ste kalendár {calendar} s {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} sprístupnil kalendár {calendar} s {user}", "{actor} unshared calendar {calendar} from you" : "{actor} vám prestal sprístupňovať kalendár {calendar}", "You unshared calendar {calendar} from {user}" : "Prestali ste sprístupňovať kalendár {calendar} od používateľa {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} zrušil zdieľanie kalendára {calendar} s používateľom {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} zrušil zdieľanie kalendára {calendar} so sebou samým", "You shared calendar {calendar} with group {group}" : "Sprístupnili ste kalendár {calendar} so skupinou {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} vyzdieľal kalendár {calendar} so skupinou {group}", "You unshared calendar {calendar} from group {group}" : "Zrušili ste zdieľanie kalendára {calendar} so skupinou {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} zrušil zdieľanie kalendára {calendar} so skupinou {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} vytvoril udalosť {event} v kalendári {calendar}", "You created event {event} in calendar {calendar}" : "Vytvorili ste udalosť [event] v kalendári [calendar]", "{actor} deleted event {event} from calendar {calendar}" : "[actor] zmazal udalosť [event] z kalendára [calendar]", "You deleted event {event} from calendar {calendar}" : "Zmazali ste udalosť [event] z kalendára [calendar]", "{actor} updated event {event} in calendar {calendar}" : "{actor} aktualizoval udalosť {event} v kalendári {calendar}", "You updated event {event} in calendar {calendar}" : "Aktualizovali ste udalosť {event} v kalendári {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} vytvoril úlohu {todo} v {calendar}", "You created todo {todo} in list {calendar}" : "Vytvorili ste úlohu {todo} v {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} zmazal úlohu {todo} z {calendar}", "You deleted todo {todo} from list {calendar}" : "Zmazali ste úlohu {todo} z {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} upravil úlohu {todo} v {calendar}", "You updated todo {todo} in list {calendar}" : "Upravili ste úlohu {todo} v {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} vyriešil úlohu {todo} v {calendar}", "You solved todo {todo} in list {calendar}" : "Vyriešili ste úlohu {todo} v {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} znovu otvoril úlohu {todo} v {calendar}", "You reopened todo {todo} in list {calendar}" : "Otvorili ste znovu úlohu {todo} v {calendar}", "A <strong>calendar</strong> was modified" : "<strong>kalendár</strong> bol upravený", "A calendar <strong>event</strong> was modified" : "<strong>Udalosť</strong> v kalendári bola upravená", "A calendar <strong>todo</strong> was modified" : "<>", "Contact birthdays" : "Narodeniny kontaktu", "Personal" : "Osobné", "Contacts" : "Kontakty", "Technical details" : "Technické podrobnosti", "Remote Address: %s" : "Vzdialená adresa: %s", "Request ID: %s" : "ID požiadavky: %s" },"pluralForm" :"nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" } l10n/nl.json 0000604 00000010107 15247100403 0006611 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Te doen", "{actor} created calendar {calendar}" : "{actor} creëerde agenda {calendar}", "You created calendar {calendar}" : "Jij creëerde agenda {calendar}", "{actor} deleted calendar {calendar}" : "{actor} verwijderde agenda {calendar}", "You deleted calendar {calendar}" : "Je verwijderde agenda {calendar}", "{actor} updated calendar {calendar}" : "{actor} werkte agenda {calendar} bij", "You updated calendar {calendar}" : "Je werkte agenda {calendar} bij", "{actor} shared calendar {calendar} with you" : "{actor} deelde agenda {calendar} met jou", "You shared calendar {calendar} with {user}" : "Je deelde agenda {calendar} met {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} deelde agenda {calendar} met {user}", "{actor} unshared calendar {calendar} from you" : "{actor} stopte het delen van agenda {calendar} met jou", "You unshared calendar {calendar} from {user}" : "Je stopte met delen agenda {calendar} met {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} stopte met delen agenda {calendar} met {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} stopte het delen van agenda {calendar} met zichzelf", "You shared calendar {calendar} with group {group}" : "Je deelde agenda {calendar} met groep {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} deelde agenda {calendar} met groep {group}", "You unshared calendar {calendar} from group {group}" : "Je stopte het delen van agenda {calendar} met groep {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} stopte het delen van agenda {calendar} met groep {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creëerde afspraak {event} in agenda {calendar}", "You created event {event} in calendar {calendar}" : "Je creëerde afspraak {event} in agenda {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} verwijderde afspraak {event} uit agenda {calendar}", "You deleted event {event} from calendar {calendar}" : "Je verwijderde afspraak {event} uit agenda {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} heeft afspraak {event} in agenda {calendar} bijgewerkt", "You updated event {event} in calendar {calendar}" : "Je hebt afspraak {event} in agenda {calendar} bijgewerkt", "{actor} created todo {todo} in list {calendar}" : "{actor} creëerde taak {todo} in lijst {calendar}", "You created todo {todo} in list {calendar}" : "Jij creëerde taak {todo} in lijst {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} verwijderde taak {todo} uit lijst {calendar}", "You deleted todo {todo} from list {calendar}" : "Je verwijderde taak {todo} uit lijst {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} heeft taak {todo} bijgewerkt in lijst {calendar}", "You updated todo {todo} in list {calendar}" : "Je hebt taak {todo} bijgewerkt in lijst {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} heeft taak {todo} in lijst {calendar} afgewerkt", "You solved todo {todo} in list {calendar}" : "Je hebt taak {todo} in lijst {calendar} afgewerkt", "{actor} reopened todo {todo} in list {calendar}" : "{actor} heropende taak {todo} in lijst {calendar}", "You reopened todo {todo} in list {calendar}" : "Je heropende taak {todo} in lijst {calendar}", "A <strong>calendar</strong> was modified" : "Een <strong>agenda</strong> is aangepast", "A calendar <strong>event</strong> was modified" : "Een agenda <strong>gebeurtenis</strong> is aangepast", "A calendar <strong>todo</strong> was modified" : "Een agenda <strong>Te doen</strong> was aangepast", "Contact birthdays" : "Verjaardagen", "Personal" : "Persoonlijk", "Contacts" : "Contactpersonen", "Technical details" : "Technische details", "Remote Address: %s" : "Extern adres: %s", "Request ID: %s" : "Aanvraag-ID: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/hu_HU.json 0000604 00000011115 15247100403 0007210 0 ustar 00 { "translations": { "Calendar" : "Naptár", "Todos" : "Teendők", "{actor} created calendar {calendar}" : "{actor} létrehozta a naptárt: {calendar}", "You created calendar {calendar}" : "Létrehoztad a naptárt: {calendar}", "{actor} deleted calendar {calendar}" : "{actor} törölte a naptárt: {calendar}", "You deleted calendar {calendar}" : "Törölted a naptárt: {calendar}", "{actor} updated calendar {calendar}" : "{actor} frissítette a napárt: {calendar}", "You updated calendar {calendar}" : "Frissítetted a naptárt: {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} megosztotta veled ezt a naptárt: {calendar}", "You shared calendar {calendar} with {user}" : "Megosztottad ezt a napárt: {calendar} vele: {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} megosztotta ezt a napárt: {calendar} vele: {user}", "{actor} unshared calendar {calendar} from you" : "{actor} visszavonta töled a naptár megosztását: {calendar}", "You unshared calendar {calendar} from {user}" : "Visszavontad a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} visszavonta a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} visszavonta tőlük a naptár megosztását: {calendar}", "You shared calendar {calendar} with group {group}" : "Megosztottad ezt a naptárt: {calendar} evvel a csoporttal: {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} megosztotta ezt a naptárt: {calendar} evvel a csoporttal: {group}", "You unshared calendar {calendar} from group {group}" : "Visszavontad ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} visszavonta ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} létrehozta ezt az eseményt: {event} ebben a naptárban: {calendar}", "You created event {event} in calendar {calendar}" : "Létrehoztad ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} törölte ezt az eseményt: {event} ebből a naptárból: {calendar}", "You deleted event {event} from calendar {calendar}" : "Törölted ezt az eseményt: {event} ebből a naptárból: {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} frissítette ezt az eseményt: {event} ebben a naptárban: {calendar}", "You updated event {event} in calendar {calendar}" : "Frissítetted ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} létrehozta ezt a teendőt: {todo} ebben a listában: {calendar}", "You created todo {todo} in list {calendar}" : "Létrehoztad ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} törölte ezt a teendőt: {todo} ebből a listából: {calendar}", "You deleted todo {todo} from list {calendar}" : "Törölted ezt a teendőt: {todo} ebből a listából: {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} frissítette ezt a teendőt: {todo} ebben a listában: {calendar}", "You updated todo {todo} in list {calendar}" : "Frissítetted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} elintézte ezt a teendőt: {todo} ebben a listában: {calendar}", "You solved todo {todo} in list {calendar}" : "Elintézted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} újranyitotta ezt a teendőt: {todo} ebben a listában: {calendar}", "You reopened todo {todo} in list {calendar}" : "Újranyitottad ezt a teendőt: {todo} ebben a listában: {calendar}", "A <strong>calendar</strong> was modified" : "Egy <strong>naptár</strong> megváltozott", "A calendar <strong>event</strong> was modified" : "Egy naptár <strong>esemény</strong> megváltozott", "A calendar <strong>todo</strong> was modified" : "Egy naptár <strong>teendő</strong> megváltozott", "Contact birthdays" : "Születésnapok", "Personal" : "Személyes", "Contacts" : "Névjegyek", "Technical details" : "Technikai adatok", "Remote Address: %s" : "Távoli cím: %s", "Request ID: %s" : "Kérelem azonosító: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/el.js 0000604 00000013452 15247100403 0006251 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Ημερολόγιο", "Todos" : "Εργασίες προς εκτέλεση", "{actor} created calendar {calendar}" : "{actor} δημιουργήθηκε το ημερολόγιο {calendar}", "You created calendar {calendar}" : "Δημιουργήσατε ημερολόγιο {ημερολόγιο}", "{actor} deleted calendar {calendar}" : "{actor} διέγραψε το ημερολόγιο {calendar}", "You deleted calendar {calendar}" : "Διαγράψατε το ημερολόγιο {calendar}", "{actor} updated calendar {calendar}" : "{actor} ενημέρωσε το ημερολόγιο {calendar}", "You updated calendar {calendar}" : "Έχετε ενημερώσει το ημερολόγιο {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} διαμοιράστηκε το ημερολόγιο {calendar} με εσάς", "You shared calendar {calendar} with {user}" : "Διαμοιραστήκατε το ημερολογίου {calendar} με {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} διαμοίρασε το ημερολόγιο {calendar} με {user}", "{actor} unshared calendar {calendar} from you" : "Ο {actor} σταμάτησε τον διαμοιρασμό του ημερολογίου {calendar} από εσάς", "You unshared calendar {calendar} from {user}" : "Σταματήσατε τον διαμοιρασμό ημερολογίου {calendar} από {user}", "{actor} unshared calendar {calendar} from {user}" : "Ο {actor} σταμάτησε τον διαμοιρασμό του ημερολογίου {calendar} από τον χρήστη {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} σταμάτησε το διαμοιρασμένο ημερολόγιο {calendar} από τον εαυτό τους", "You shared calendar {calendar} with group {group}" : "Διαμοιραστείκατε ένα ημερολόγιο {calendar} με την ομάδα {group}", "{actor} shared calendar {calendar} with group {group}" : "Ο {actor} διαμοιράστηκε το ημερολόγιο {calendar} με την ομάδα {group}", "You unshared calendar {calendar} from group {group}" : "Σταματήσατε τον διαμοιρασμό του ημερολογίου {calendar} από την ομάδα {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} σταμάτησε το διαμοιρασμένο ημερολόγιο {calendar} από την ομάδα {group}", "{actor} created event {event} in calendar {calendar}" : "Ο {actor} δημιούργησε το γεγονός {event} στο ημερολόγιο {calendar}", "You created event {event} in calendar {calendar}" : "Δημιουργήσατε το γεγονός {event} στο ημερολόγιο {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "Ο {actor} διέγραψε το γεγονός {event} από το ημερολόγιο {calendar}", "You deleted event {event} from calendar {calendar}" : "Διαγράψατε το συμβάν {event} από το ημερολόγιο {calendar}", "{actor} updated event {event} in calendar {calendar}" : "Ο {actor} ενημέρωσε το γεγονός {event} στο ημερολόγιο {calendar}", "You updated event {event} in calendar {calendar}" : "Ενημερώσατε το συμβάν {event} στο ημερολόγιο {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} δημιούργησε την εκκρεμότητα {todo} στη λίστα {ημερολόγιο}", "You created todo {todo} in list {calendar}" : "Δημιουργήσατε την εκκρεμότητα {todo} στη λίστα {ημερολόγιο}", "{actor} deleted todo {todo} from list {calendar}" : "Ο {actor} διέγραψε την εκκρεμότητα {todo} από τη λίστα {ημερολόγιο}", "You deleted todo {todo} from list {calendar}" : "Διέγραψες την εκκρεμότητα {todo} από τη λίστα {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} ενημέρωσε την εκκρεμότητα {todo} στη λίστα {calendar}", "You updated todo {todo} in list {calendar}" : "Ενημέρωσες την εκκρεμότητα {todo} στη λίστα {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} επίλυσε την εκκρεμότητα {todo} στην λίστα {calendar}", "You solved todo {todo} in list {calendar}" : "Επίλυσες την εκκρεμότητα {todo} στην λίστα {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} άνοιξε ξανά την εκκρεμότητα {todo} στην λίστα {calendar}", "You reopened todo {todo} in list {calendar}" : "Άνοιξες ξανά την εκκρεμότητα {todo} στην λίστα {calendar}", "A <strong>calendar</strong> was modified" : "Τροποποιήθηκε ένα <strong>ημερολόγιο</strong> ", "A calendar <strong>event</strong> was modified" : "Τροποποιήθηκε ένα <strong>γεγονός</strong> του ημερολογίου", "A calendar <strong>todo</strong> was modified" : "Ενός ημερολογίου η <strong>εκκρεμότητα</strong> τροποποιήθηκε", "Contact birthdays" : "Γενέθλια επαφών", "Personal" : "Προσωπικά", "Contacts" : "Επαφές", "Technical details" : "Τεχνικές λεπτομέρειες", "Remote Address: %s" : "Απομακρυσμένη Διεύθυνση: %s", "Request ID: %s" : "ID Αιτήματος: %s" }, "nplurals=2; plural=(n != 1);"); l10n/ko.json 0000604 00000010636 15247100403 0006620 0 ustar 00 { "translations": { "Calendar" : "달력", "Todos" : "할 일", "{actor} created calendar {calendar}" : "{actor} 님이 달력 {calendar}을(를) 생성함", "You created calendar {calendar}" : "달력 {calendar}을(를) 생성함", "{actor} deleted calendar {calendar}" : "{actor} 님이 달력 {calendar}을(를) 삭제함", "You deleted calendar {calendar}" : "달력 {calendar}을(를) 삭제함", "{actor} updated calendar {calendar}" : "{actor} 님이 달력 {calendar}을(를) 업데이트함", "You updated calendar {calendar}" : "달력 {calendar}을(를) 업데이트함", "{actor} shared calendar {calendar} with you" : "{actor} 님이 내게 달력 {calendar}을(를) 공유함", "You shared calendar {calendar} with {user}" : "{user} 님과 달력 {calendar}을(를) 공유함", "{actor} shared calendar {calendar} with {user}" : "{actor} 님이 {user} 님과 달력 {calendar}을(를) 공유함", "{actor} unshared calendar {calendar} from you" : "{actor} 님이 달력 {calendar}의 공유를 중단함", "You unshared calendar {calendar} from {user}" : "{user} 님과 달력 {calendar}의 공유를 중단함", "{actor} unshared calendar {calendar} from {user}" : "{actor} 님이 {user} 님과 {calendar} 달력의 공유를 중단함", "{actor} unshared calendar {calendar} from themselves" : "{actor} 님이 달력 {calendar}의 공유를 해제함", "You shared calendar {calendar} with group {group}" : "달력 {calendar}을(를) 그룹 {group}와(과) 공유함", "{actor} shared calendar {calendar} with group {group}" : "{actor} 님이 달력 {calendar}을(를) 그룹 {group}와(과) 공유함", "You unshared calendar {calendar} from group {group}" : "달력 {calendar}을(를) 그룹 {group}와(과) 공유하지 않음", "{actor} unshared calendar {calendar} from group {group}" : "{actor} 님이 달력 {calendar}을(를) 그룹 {group}와(과) 공유하지 않음", "{actor} created event {event} in calendar {calendar}" : "{actor} 님이 행사 {event}을(를) 달력 {calendar}에 생성함", "You created event {event} in calendar {calendar}" : "행사 {event}을(를) 달력 {calendar}에 생성함", "{actor} deleted event {event} from calendar {calendar}" : "{actor} 님이 행사 {event}을(를) 달력 {calendar}에서 삭제함", "You deleted event {event} from calendar {calendar}" : "행사 {event}을(를) 달력 {calendar}에서 삭제함", "{actor} updated event {event} in calendar {calendar}" : "{actor} 님이 달력 {calendar}의 행사 {event}을(를) 업데이트함", "You updated event {event} in calendar {calendar}" : "달력 {calendar}의 행사 {event}을(를) 업데이트함", "{actor} created todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}에 할 일 {todo}을(를) 생성함", "You created todo {todo} in list {calendar}" : "목록 {calendar}에 할 일 {todo}을(를) 생성함", "{actor} deleted todo {todo} from list {calendar}" : "{actor} 님이 목록 {calendar}에서 할 일 {todo}을(를) 삭제함", "You deleted todo {todo} from list {calendar}" : "목록 {calendar}에서 할 일 {todo}을(를) 삭제함", "{actor} updated todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}의 할 일 {todo}을(를) 업데이트함", "You updated todo {todo} in list {calendar}" : "목록 {calendar}의 할 일 {todo}을(를) 업데이트함", "{actor} solved todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}의 할 일 {todo}을(를) 끝냄", "You solved todo {todo} in list {calendar}" : "목록 {calendar}의 할 일 {todo}을(를) 끝냄", "{actor} reopened todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}의 할 일 {todo}을(를) 다시 염", "You reopened todo {todo} in list {calendar}" : "목록 {calendar}의 할 일 {todo}을(를) 다시 염", "A <strong>calendar</strong> was modified" : "<strong>달력</strong>이 수정됨", "A calendar <strong>event</strong> was modified" : "달력 <strong>행사</strong>가 수정됨", "A calendar <strong>todo</strong> was modified" : "달력의 <strong>할 일</strong>이 수정됨", "Contact birthdays" : "연락처에 등록된 생일", "Personal" : "개인", "Contacts" : "연락처", "Technical details" : "기술 정보", "Remote Address: %s" : "원격 주소: %s", "Request ID: %s" : "요청 ID: %s" },"pluralForm" :"nplurals=1; plural=0;" } l10n/fr.js 0000604 00000010626 15247100403 0006260 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Agenda", "Todos" : "Tâches", "{actor} created calendar {calendar}" : "{actor} a créé l'agenda {calendar}", "You created calendar {calendar}" : "Vous avez créé l'agenda {calendar}", "{actor} deleted calendar {calendar}" : "{actor} a supprimé l'agenda {calendar}", "You deleted calendar {calendar}" : "Vous avez supprimé l'agenda {calendar}", "{actor} updated calendar {calendar}" : "{actor} a mis à jour l'agenda {calendar}", "You updated calendar {calendar}" : "Vous avez mis à jour l'agenda {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} a partagé l'agenda {calendar} avec vous", "You shared calendar {calendar} with {user}" : "Vous avez partagé l'agenda {calendar} avec {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} a partagé l'agenda {calendar} avec {user}", "{actor} unshared calendar {calendar} from you" : "{actor} a cessé de partager l'agenda {calendar} avec vous", "You unshared calendar {calendar} from {user}" : "Vous avez cessé de partager l'agenda {calendar} avec {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} a cessé de partager l'agenda {calendar} avec {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} a cessé de partager l'agenda {calendar} avec lui-même", "You shared calendar {calendar} with group {group}" : "Vous avez partagé l'agenda {calendar} avec le groupe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} a partagé l'agenda {calendar} avec le groupe {group}", "You unshared calendar {calendar} from group {group}" : "Vous avez cessé de partager l'agenda {calendar} avec le groupe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} a cessé de partager l'agenda {calendar} avec le groupe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} a créé l'évènement {event} dans l'agenda {calendar}", "You created event {event} in calendar {calendar}" : "Vous avez créé l'évènement {event} dans l'agenda {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} a supprimé l'évènement {event} de l'agenda {calendar}", "You deleted event {event} from calendar {calendar}" : "Vous avez supprimé l'évènement {event} de l'agenda {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} a mis à jour l'évènement {event} dans l'agenda {calendar}", "You updated event {event} in calendar {calendar}" : "Vous avez mis à jour l'évènement {event} dans l'agenda {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} a créé la tâche {todo} dans la liste {calendar}", "You created todo {todo} in list {calendar}" : "Vous avez créé la tâche {todo} dans la liste {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} a supprimé la tâche {todo} de la liste {calendar}", "You deleted todo {todo} from list {calendar}" : "Vous avez supprimé la tâche {todo} de la liste {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} a mis à jour la tâche {todo} dans la liste {calendar}", "You updated todo {todo} in list {calendar}" : "Vous avez mis à jour la tâche {todo} dans la liste {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} a terminé la tâche {todo} de la liste {calendar}", "You solved todo {todo} in list {calendar}" : "Vous avez terminé la tâche {todo} de la liste {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} a réouvert la tâche {todo} dans la liste {calendar}", "You reopened todo {todo} in list {calendar}" : "Vous avez réouvert la tâche {todo} dans la liste {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>agenda</strong> a été modifié", "A calendar <strong>event</strong> was modified" : "Un <strong>événement</strong> de l'agenda a été modifié", "A calendar <strong>todo</strong> was modified" : "Une <strong>liste de tâches</strong> de l'agenda a été modifiée", "Contact birthdays" : "Anniversaires des contacts", "Personal" : "Personnel", "Contacts" : "Contacts", "Technical details" : "Détails techniques", "Remote Address: %s" : "Adresse distante : %s", "Request ID: %s" : "ID de la requête : %s" }, "nplurals=2; plural=(n > 1);"); l10n/en_GB.js 0000604 00000007712 15247100403 0006625 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendar", "Todos" : "Todos", "{actor} created calendar {calendar}" : "{actor} created calendar {calendar}", "You created calendar {calendar}" : "You created calendar {calendar}", "{actor} deleted calendar {calendar}" : "{actor} deleted calendar {calendar}", "You deleted calendar {calendar}" : "You deleted calendar {calendar}", "{actor} updated calendar {calendar}" : "{actor} updated calendar {calendar}", "You updated calendar {calendar}" : "You updated calendar {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} shared calendar {calendar} with you", "You shared calendar {calendar} with {user}" : "You shared calendar {calendar} with {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} shared calendar {calendar} with {user}", "{actor} unshared calendar {calendar} from you" : "{actor} unshared calendar {calendar} from you", "You unshared calendar {calendar} from {user}" : "You unshared calendar {calendar} from {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} unshared calendar {calendar} from {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} unshared calendar {calendar} from themselves", "You shared calendar {calendar} with group {group}" : "You shared calendar {calendar} with group {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} shared calendar {calendar} with group {group}", "You unshared calendar {calendar} from group {group}" : "You unshared calendar {calendar} from group {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} unshared calendar {calendar} from group {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} created event {event} in calendar {calendar}", "You created event {event} in calendar {calendar}" : "You created event {event} in calendar {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} deleted event {event} from calendar {calendar}", "You deleted event {event} from calendar {calendar}" : "You deleted event {event} from calendar {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} updated event {event} in calendar {calendar}", "You updated event {event} in calendar {calendar}" : "You updated event {event} in calendar {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} created todo {todo} in list {calendar}", "You created todo {todo} in list {calendar}" : "You created todo {todo} in list {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} deleted todo {todo} from list {calendar}", "You deleted todo {todo} from list {calendar}" : "You deleted todo {todo} from list {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} updated todo {todo} in list {calendar}", "You updated todo {todo} in list {calendar}" : "You updated todo {todo} in list {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} solved todo {todo} in list {calendar}", "You solved todo {todo} in list {calendar}" : "You solved todo {todo} in list {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reopened todo {todo} in list {calendar}", "You reopened todo {todo} in list {calendar}" : "You reopened todo {todo} in list {calendar}", "A <strong>calendar</strong> was modified" : "A <strong>calendar</strong> was modified", "A calendar <strong>event</strong> was modified" : "A calendar <strong>event</strong> was modified", "A calendar <strong>todo</strong> was modified" : "A calendar <strong>todo</strong> was modified", "Contact birthdays" : "Contact birthdays", "Personal" : "Personal", "Contacts" : "Contacts", "Technical details" : "Technical details", "Remote Address: %s" : "Remote Address: %s", "Request ID: %s" : "Request ID: %s" }, "nplurals=2; plural=(n != 1);"); l10n/sv.json 0000604 00000010066 15247100403 0006634 0 ustar 00 { "translations": { "Calendar" : "Kalender", "Todos" : "Uppgifter", "{actor} created calendar {calendar}" : "{actor} skapade kalender {calendar}", "You created calendar {calendar}" : "Du skapade kalender {calendar}", "{actor} deleted calendar {calendar}" : "{actor} raderade kalender {calendar}", "You deleted calendar {calendar}" : "Du raderade kalender {calendar}", "{actor} updated calendar {calendar}" : "{actor} uppdaterade kalender {calendar}", "You updated calendar {calendar}" : "Du uppdaterade kalender {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delade kalender {calendar} med dig", "You shared calendar {calendar} with {user}" : "Du delade kalender {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delade kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} slutade dela kalender {calendar} med dig", "You unshared calendar {calendar} from {user}" : "Du slutade dela kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} slutade dela kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} slutade dela kalender {calendar} med sig själv", "You shared calendar {calendar} with group {group}" : "Du delade kalender {calendar} med grupp {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delade kalender {calendar} med grupp {group}", "You unshared calendar {calendar} from group {group}" : "Du slutade dela kalender {calendar} med gruppen {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} slutade dela kalender {calendar} med gruppen {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} skapade händelse {event} i kalender {calendar}", "You created event {event} in calendar {calendar}" : "Du skapade händelse {event} i kalender {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} raderade händelse {event} från kalender {calendar}", "You deleted event {event} from calendar {calendar}" : "Du raderade händelse {event} från kalender {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} uppdaterade händelse {event} i kalender {calendar}", "You updated event {event} in calendar {calendar}" : "Du uppdaterade händelse {event} i kalender {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} skapade uppgift {todo} i listan {calendar}", "You created todo {todo} in list {calendar}" : "Du skapade uppgift {todo} i listan {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} raderade uppgift {todo} från listan {calendar}", "You deleted todo {todo} from list {calendar}" : "Du raderade uppgift {todo} från listan {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} uppdaterade uppgift {todo} i listan {calendar}", "You updated todo {todo} in list {calendar}" : "Du uppdaterade uppgift {todo} i listan {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} löste uppgift {todo} i listan {calendar}", "You solved todo {todo} in list {calendar}" : "Du löste uppgift {todo} i listan {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} återupptog uppgift {todo} i listan {calendar}", "You reopened todo {todo} in list {calendar}" : "Du återupptog uppgift {todo} i listan {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> modifierades", "A calendar <strong>event</strong> was modified" : "En kalender-<strong>händelse</strong> modifierades", "A calendar <strong>todo</strong> was modified" : "En kalender <strong>uppgift</strong> modifierades", "Contact birthdays" : "Födelsedagar", "Personal" : "Privat", "Contacts" : "Kontakter", "Technical details" : "Tekniska detaljer", "Remote Address: %s" : "Extern adress: %s", "Request ID: %s" : "Begär ID: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/it.js 0000604 00000010736 15247100403 0006267 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Calendario", "Todos" : "Cose da fare", "{actor} created calendar {calendar}" : "{actor} ha creato il calendario {calendar}", "You created calendar {calendar}" : "Hai creato il calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} ha eliminato il calendario {calendar}", "You deleted calendar {calendar}" : "Hai eliminato il calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} ha aggiornato il calendario {calendar}", "You updated calendar {calendar}" : "Hai aggiornato il calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} ha condiviso il calendario {calendar} con te", "You shared calendar {calendar} with {user}" : "Hai condiviso il calendario {calendar} con {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} ha aggiornato il calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ha rimosso la condivisione del calendario {calendar} con te", "You unshared calendar {calendar} from {user}" : "Hai rimosso la condivisione del calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} ha rimosso la condivisione del calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} ha rimosso la condivisione del calendario {calendar} con se stesso", "You shared calendar {calendar} with group {group}" : "Hai condiviso il calendario {calendar} con il gruppo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} ha condiviso il calendario {calendar} con il gruppo {group}", "You unshared calendar {calendar} from group {group}" : "Hai rimosso la condivisione del calendario {calendar} con il gruppo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} ha rimosso la condivisione del calendario {calendar} con il gruppo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} ha creato l'evento {event} nel calendario {calendar}", "You created event {event} in calendar {calendar}" : "Hai creato l'evento {event} nel calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} ha eliminato l'evento {event} dal calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Hai ha eliminato l'evento {event} dal calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} ha aggiornato l'evento {event} nel calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Hai aggiornato l'evento {event} nel calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} ha creato la cosa da fare {todo} nell'elenco {calendar}", "You created todo {todo} in list {calendar}" : "Hai creato la cosa da fare {todo} nell'elenco {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} ha eliminato la cosa da fare {todo} dall'elenco {calendar}", "You deleted todo {todo} from list {calendar}" : "Hai eliminato la cosa da fare {todo} dall'elenco {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} ha aggiornato la cosa da fare {todo} nell'elenco {calendar}", "You updated todo {todo} in list {calendar}" : "Hai aggiornato la cosa da fare {todo} nell'elenco {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} ha risolto la cosa da fare {todo} nell'elenco {calendar}", "You solved todo {todo} in list {calendar}" : "Hai risolto la cosa da fare {todo} nell'elenco {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} ha riaperto la cosa da fare {todo} nell'elenco {calendar}", "You reopened todo {todo} in list {calendar}" : "Hai riaperto la cosa da fare {todo} nell'elenco {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> è stato modificato", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> del calendario è stato modificato", "A calendar <strong>todo</strong> was modified" : "Una <strong>cosa da fare</strong> del calendario è stata modificata", "Contact birthdays" : "Date di nascita dei contatti", "Personal" : "Personale", "Contacts" : "Contatti", "Technical details" : "Dettagli tecnici", "Remote Address: %s" : "Indirizzo remoto: %s", "Request ID: %s" : "ID richiesta: %s" }, "nplurals=2; plural=(n != 1);"); l10n/ru.js 0000604 00000012205 15247100403 0006272 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Календарь", "Todos" : "Задачи", "{actor} created calendar {calendar}" : "{actor} создал календарь {calendar}", "You created calendar {calendar}" : "Вы создали календарь {calendar}", "{actor} deleted calendar {calendar}" : "{actor} удалил календарь {calendar}", "You deleted calendar {calendar}" : "Вы удалили календарь {calendar}", "{actor} updated calendar {calendar}" : "{actor} обновил календарь {calendar}", "You updated calendar {calendar}" : "Вы обновили календарь {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} поделился календарем {calendar} с вами", "You shared calendar {calendar} with {user}" : "Вы поделились календарем {calendar} с {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} поделился календарем {calendar} с {user}", "{actor} unshared calendar {calendar} from you" : "{actor} закрыл для вас доступ к календарю {calendar}", "You unshared calendar {calendar} from {user}" : "Вы закрыли доступ к календарю {calendar} для {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} закрыл доступ к календарю {calendar} для {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} закрыл доступ к календарю {calendar} для себя", "You shared calendar {calendar} with group {group}" : "Вы поделились календарем {calendar} с группой {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} поделился календарем {calendar} с группой {group}", "You unshared calendar {calendar} from group {group}" : "Вы закрыли доступ к календарю {calendar} для группы {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} закрыл доступ к календарю {calendar} для группы {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} создал событие {event} в календаре {calendar}", "You created event {event} in calendar {calendar}" : "Вы создали событие {event} в календаре {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} удалил событие {event} из календаря {calendar}", "You deleted event {event} from calendar {calendar}" : "Вы удалили событие {event} из календаря {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} обновил событие {event} в календаре {calendar}", "You updated event {event} in calendar {calendar}" : "Вы обновили событие {event} в календаре {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} создал задачу {todo} в списке {calendar}", "You created todo {todo} in list {calendar}" : "Вы создали задачу {todo} в списке {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} удалил задачу {todo} из списка {calendar}", "You deleted todo {todo} from list {calendar}" : "Вы удалили задачу {todo} из списка {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} обновил задачу {todo} из списка {calendar}", "You updated todo {todo} in list {calendar}" : "Вы обновили задачу {todo} из списка {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} завершил задачу {todo} из списка {calendar}", "You solved todo {todo} in list {calendar}" : "Вы завершили задачу {todo} из списка {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} повторно открыл задачу {todo} из списка {calendar}", "You reopened todo {todo} in list {calendar}" : "Вы повторно открыли задачу {todo} из списка {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Календарь</strong> был изменен", "A calendar <strong>event</strong> was modified" : "<strong>Событие</strong> календаря была изменена", "A calendar <strong>todo</strong> was modified" : "<strong>Задача</strong> календаря была изменена", "Contact birthdays" : "Дни рождения контакта", "Personal" : "Личное", "Contacts" : "Контакты", "Technical details" : "Технические подробности", "Remote Address: %s" : "Удаленный адрес: %s", "Request ID: %s" : "ID запроса: %s" }, "nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); l10n/pl.js 0000604 00000011043 15247100403 0006256 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalendarz", "Todos" : "Zadania", "{actor} created calendar {calendar}" : "{actor} utworzył/-a kalendarz {calendar}", "You created calendar {calendar}" : "Utworzyłeś/-aś kalendarz {calendar}", "{actor} deleted calendar {calendar}" : "{actor} usunął/-ęła kalendarz {calendar} .", "You deleted calendar {calendar}" : "Usunąłęś/ęłaś kalendarz {calendar}", "{actor} updated calendar {calendar}" : "{actor} zaktualizował/-a kalendarz {calendar} .", "You updated calendar {calendar}" : "Zaktualizowałeś/-aś kalendarz {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} współdzieli kalendarz {calendar} z Tobą", "You shared calendar {calendar} with {user}" : "Współdzielisz kalendarz {calendar} z {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} współdzieli kalendarz {calendar} z {user}", "{actor} unshared calendar {calendar} from you" : "{actor} zakończył/-a współdzielienie kalendarza {calendar} z Tobą", "You unshared calendar {calendar} from {user}" : "Zakończyłeś/-aś współdzielenie kalendarza {calendar} z {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} zakończył/-a współdzielenie kalendarza {calendar} z {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} zakończył/-a wspóldzielenie kalendarza {calendar} od siebie", "You shared calendar {calendar} with group {group}" : "Współdzielisz kalendarz {calendar} z grupą {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} współdzieli kalendarz {calendar} z grupą {group}", "You unshared calendar {calendar} from group {group}" : "Zakończyłeś współdzielenie kalendarza {calendar} z grupą {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} nie współdzieli kalendarza {calendar} z grupą {group} .", "{actor} created event {event} in calendar {calendar}" : "{actor} utworzył/-a wydarzenie {event} w kalendarzu {calendar}", "You created event {event} in calendar {calendar}" : "Utworzyłeś/-ał wydarzenie {event} w kalendarzu {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} usunął/-ęłą wydarzenie {event} z kalendarza {calendar}", "You deleted event {event} from calendar {calendar}" : "Usunąłeś/-aś wydarzenie {event} z kalendarza {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} zaktualizował/-a wydarzenie {event} z kalendarza {calendar}", "You updated event {event} in calendar {calendar}" : "Zaktualizowałeś/-aś wydarzenie {event} w kalendarzu {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} utworzył/-a zadanie {todo} na liście {calendar}", "You created todo {todo} in list {calendar}" : "Utworzyłeś/-aś zadanie {todo} na liście {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} usunął/-ęła zadanie {todo} z listy {calendar}", "You deleted todo {todo} from list {calendar}" : "Usunąłeś/-ęłaś zadanie {todo} z listy {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} zaktualizował/-a zadanie {todo} na liście {calendar}", "You updated todo {todo} in list {calendar}" : "Zaktualizowałeś/-aś zadanie {todo} na liście {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} zakończył zadanie {todo} na liście {calendar}", "You solved todo {todo} in list {calendar}" : "Zakończyłeś/-aś zadanie {todo} na liście {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} otworzył ponownie zadanie {todo} na liście {calendar}", "You reopened todo {todo} in list {calendar}" : "Otworzyłęś/-aś ponownie zadanie {todo} na liście {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Kalendarz</strong> został zmodyfikowany", "A calendar <strong>event</strong> was modified" : "<strong>Zdarzenie</strong> kalendarza zostało zmodyfikowane", "A calendar <strong>todo</strong> was modified" : "Kalendarz <strong>zadań</strong> został zmieniony", "Contact birthdays" : "Urodziny kontaktu", "Personal" : "Osobiste", "Contacts" : "Kontakty", "Technical details" : "Szczegóły techniczne", "Remote Address: %s" : "Adres zdalny: %s", "Request ID: %s" : "ID żądania: %s" }, "nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);"); l10n/nb.js 0000604 00000010276 15247100403 0006251 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalender", "Todos" : "Gjøremål", "{actor} created calendar {calendar}" : "{actor} opprettet kalenderen {calendar}", "You created calendar {calendar}" : "Du opprettet kalenderen {calendar}", "{actor} deleted calendar {calendar}" : "{actor} slettet kalenderen {calendar}", "You deleted calendar {calendar}" : "Du slettet kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} oppdaterte kalenderen {calendar}", "You updated calendar {calendar}" : "Du oppdaterte kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med deg", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", "{actor} unshared calendar {calendar} from you" : "{actor} fjernet delingen av kalenderen {calendar} med deg", "You unshared calendar {calendar} from {user}" : "Du fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} fjernet delingen av kalender {calendar} med {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} fjernet delingen av kalender {calendar} med seg selv", "You shared calendar {calendar} with group {group}" : "Du delte kalender {calendar} med gruppe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} delte kalenderen {calendar} med gruppe {group}", "You unshared calendar {calendar} from group {group}" : "Du fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} fjernet deling av kalenderen {calendar} med gruppe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} opprettet en hendelse {event} i kalenderen {calendar}", "You created event {event} in calendar {calendar}" : "Du opprettet en hendelse {event} i kalenderen {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} slettet hendelsen {event} fra kalenderen {calendar}", "You deleted event {event} from calendar {calendar}" : "Du slettet hendelsen {event} fra kalenderen {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} oppdaterte hendelsen {event} i kalenderen {calendar}", "You updated event {event} in calendar {calendar}" : "Du oppdaterte hendelsen {event} i kalenderen {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} opprettet en oppgaven {todo} i listen {calendar}", "You created todo {todo} in list {calendar}" : "Du opprettet en oppgave {todo} i listen {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} slettet gjøremålet {todo} fra listen {calendar}", "You deleted todo {todo} from list {calendar}" : "Du slettet gjøremålet {todo} fra listen {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} oppdaterte gjøremålet {todo} i listen {calendar}", "You updated todo {todo} in list {calendar}" : "Du oppdaterte gjøremålet {todo} i listen {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} ferdigstilte gjøremålet {todo} i listen {calendar}", "You solved todo {todo} in list {calendar}" : "Du ferdigstilte gjøremålet {todo} i listen {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} gjenåpnet gjøremålet {todo} i listen {calendar}", "You reopened todo {todo} in list {calendar}" : "Du gjenåpnet oppgaven {todo} i listen {calendar}", "A <strong>calendar</strong> was modified" : "En <strong>kalender</strong> ble endret", "A calendar <strong>event</strong> was modified" : "En kalender <strong>hendelse</strong> ble endret", "A calendar <strong>todo</strong> was modified" : "En kalende <strong>gjøremål</strong> ble endret", "Contact birthdays" : "Kontakters fødelsdag", "Personal" : "Personlig", "Contacts" : "Kontakter", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Ekstern adresse: %s", "Request ID: %s" : "Forespørsel ID: %s" }, "nplurals=2; plural=(n != 1);"); l10n/tr.js 0000604 00000010463 15247100403 0006275 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Takvim", "Todos" : "Yapılacak İşler", "{actor} created calendar {calendar}" : "{actor}, {calendar} takvimini ekledi", "You created calendar {calendar}" : "{calendar} takvimini eklediniz", "{actor} deleted calendar {calendar}" : "{actor}, {calendar} takvimini sildi", "You deleted calendar {calendar}" : "{calendar} takvimini sildiniz", "{actor} updated calendar {calendar}" : "{actor}, {calendar} takvimini güncelledi", "You updated calendar {calendar}" : "{calendar} takvimini güncellediniz", "{actor} shared calendar {calendar} with you" : "{actor}, {calendar} takvimini sizinle paylaştı", "You shared calendar {calendar} with {user}" : "{calendar} takvimini {user} ile paylaştınız", "{actor} shared calendar {calendar} with {user}" : "{actor}, {user} ile {calendar} takvimini paylaştı", "{actor} unshared calendar {calendar} from you" : "{actor}, {calendar} takviminin sizinle paylaşımını kaldırdı", "You unshared calendar {calendar} from {user}" : "{calendar} takviminin {user} ile paylaşımını kaldırdınız", "{actor} unshared calendar {calendar} from {user}" : "{actor}, {calendar} takviminin {user} ile paylaşımını kaldırdı", "{actor} unshared calendar {calendar} from themselves" : "{actor}, {calendar} takviminin kendisi ile paylaşımını kaldırdı", "You shared calendar {calendar} with group {group}" : "{calendar} takvimini {group} grubu ile paylaştınız", "{actor} shared calendar {calendar} with group {group}" : "{actor}, {calendar} takvimini {group} grubu ile paylaştı", "You unshared calendar {calendar} from group {group}" : "{calendar} takviminin {group} grubu ile paylaşımını kaldırdınız", "{actor} unshared calendar {calendar} from group {group}" : "{actor}, {calendar} takviminin {group} grubu ile paylaşımını kaldırdı", "{actor} created event {event} in calendar {calendar}" : "{actor}, {calendar} takvimine {event} etkinliğini ekledi", "You created event {event} in calendar {calendar}" : "{calendar} takvimine {event} etkinliğini eklediniz", "{actor} deleted event {event} from calendar {calendar}" : "{actor}, {calendar} takviminden {event} etkinliğini sildi", "You deleted event {event} from calendar {calendar}" : "{calendar} takviminden {event} etkinliğini sildiniz", "{actor} updated event {event} in calendar {calendar}" : "{actor}, {calendar} takvimindeki {event} etkinliğini güncelledi", "You updated event {event} in calendar {calendar}" : "{calendar} takvimindeki {event} etkinliğini güncellediniz", "{actor} created todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesine {todo} işini ekledi", "You created todo {todo} in list {calendar}" : "{calendar} takvimi listesine {todo} işini eklediniz", "{actor} deleted todo {todo} from list {calendar}" : "{actor}, {calendar} takvimi listesinden {todo} işini sildi", "You deleted todo {todo} from list {calendar}" : "{calendar} takvimi listesinden {todo} işini sildiniz", "{actor} updated todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesindeki {todo} işini güncelledi", "You updated todo {todo} in list {calendar}" : "{calendar} takvimi listesindeki {todo} işini güncellediniz", "{actor} solved todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesindeki {todo} işini tamamladı", "You solved todo {todo} in list {calendar}" : "{calendar} takvimi listesindeki {todo} işini tamamladınız", "{actor} reopened todo {todo} in list {calendar}" : "{actor}, {calendar} takvimi listesindeki {todo} işini yeniden başlattı", "You reopened todo {todo} in list {calendar}" : "{calendar} takvimi listesindeki {todo} işini yeniden başlattınız", "A <strong>calendar</strong> was modified" : "Bir <strong>takvim</strong> düzenlendi", "A calendar <strong>event</strong> was modified" : "Bir <strong>etkinlik</strong> düzenlendi", "A calendar <strong>todo</strong> was modified" : "Bir takvim <strong>işi</strong> düzenlendi", "Contact birthdays" : "Kişi doğum günleri", "Personal" : "Kişisel", "Contacts" : "Kişiler", "Technical details" : "Teknik ayrıntılar", "Remote Address: %s" : "Uzak Adres: %s", "Request ID: %s" : "İstek Kodu: %s" }, "nplurals=2; plural=(n > 1);"); l10n/bg_BG.json 0000604 00000011577 15247100403 0007154 0 ustar 00 { "translations": { "Calendar" : "Календар", "Todos" : "Задачи", "{actor} created calendar {calendar}" : "{actor} направи календар {calendar}", "You created calendar {calendar}" : "Направихте календар {calendar}", "{actor} deleted calendar {calendar}" : "{actor} изтри календар {calendar}", "You deleted calendar {calendar}" : "Изтрихте календар {calendar}", "{actor} updated calendar {calendar}" : "{actor} обнови календар {calendar}", "You updated calendar {calendar}" : "Обновихте календар {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} сподели календар {calendar} с теб", "You shared calendar {calendar} with {user}" : "Споделихте календар {calendar} с {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} сподели календар {calendar} с {user}", "{actor} unshared calendar {calendar} from you" : "{actor} отказа споделяне на календар {calendar} с теб", "You unshared calendar {calendar} from {user}" : "Отказахте споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} отказа споделяне на календар {calendar} от {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} отказа споделяне на календар {calendar} от себеси", "You shared calendar {calendar} with group {group}" : "Споделихте календар {calendar} с група {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} сподели календар {calendar} с група {group}", "You unshared calendar {calendar} from group {group}" : "Отказахте споделяне на календар {calendar} от група {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} отказа споделяне с календар {calendar} от група {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} създаде събитие {event} в календар {calendar}", "You created event {event} in calendar {calendar}" : "Създадохте събитие {event} в календар {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} изтри събитие {event} от календар {calendar}", "You deleted event {event} from calendar {calendar}" : "Изтрихте събитие {event} от календар {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} обнови събитие {event} в календар {calendar}", "You updated event {event} in calendar {calendar}" : "Обновихте събитие {event} в календар {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} създаде задача {todo} в списък {calendar}", "You created todo {todo} in list {calendar}" : "Създадохте задача {todo} в списък {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} изтри задача {todo} от списък {calendar}", "You deleted todo {todo} from list {calendar}" : "Изтрихте задача {todo} от лист {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} актуализира задача {todo} в списък {calendar}", "You updated todo {todo} in list {calendar}" : "Променихте задача {todo} в списък {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} реши задача {todo} в списък {calendar}", "You solved todo {todo} in list {calendar}" : "Решихте задача {todo} в списък {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} възобнови задача {todo} в списък {calendar}", "You reopened todo {todo} in list {calendar}" : "Възобновихте задача {todo} в списък {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Календар</strong> беше променен", "A calendar <strong>event</strong> was modified" : "Календарно <strong>събитие</strong> беше променено", "A calendar <strong>todo</strong> was modified" : "Календарна <strong>задача</strong> беше променена", "Contact birthdays" : "Рождени дни на контакти", "Personal" : "Личен", "Contacts" : "Контакти", "Technical details" : "Технически детайли", "Remote Address: %s" : "Отдалечен адрес: %s", "Request ID: %s" : "ID на заявка: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/is.json 0000604 00000010467 15247100403 0006624 0 ustar 00 { "translations": { "Calendar" : "Dagatal", "Todos" : "Verkþættir", "{actor} created calendar {calendar}" : "{actor} bjó til dagatalið {calendar}", "You created calendar {calendar}" : "Þú bjóst til dagatalið {calendar}", "{actor} deleted calendar {calendar}" : "{actor} eyddi dagatalinu {calendar}", "You deleted calendar {calendar}" : "Þú eyddir dagatalinu {calendar}", "{actor} updated calendar {calendar}" : "{actor} uppfærði dagatalið {calendar}", "You updated calendar {calendar}" : "Þú uppfærðir dagatalið {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} deildi dagatalinu {calendar} með þér", "You shared calendar {calendar} with {user}" : "Þú deildir dagatalinu {calendar} með {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} deildi dagatalinu {calendar} með {user}", "{actor} unshared calendar {calendar} from you" : "{actor} tók af deilingu á dagatalinu {calendar} frá þér", "You unshared calendar {calendar} from {user}" : "Þú tókst af deilingu á dagatalinu {calendar} frá {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} tók af deilingu á dagatalinu {calendar} frá {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} tók af deilingu á dagatalinu {calendar} frá þeim sjálfum", "You shared calendar {calendar} with group {group}" : "Þú deildir dagatalinu {calendar} með hópnum {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} deildi dagatalinu {calendar} með hópnum {group}", "You unshared calendar {calendar} from group {group}" : "Þú tókst af deilingu á dagatalinu {calendar} frá hópnum {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} tók af deilingu á dagatalinu {calendar} frá hópnum {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} bjó til atburðinn {event} í dagatalinu {calendar}", "You created event {event} in calendar {calendar}" : "Þú bjóst til atburðinn {event} í dagatalinu {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} eyddi atburðinum {event} úr dagatalinu {calendar}", "You deleted event {event} from calendar {calendar}" : "Þú eyddir atburðinum {event} úr dagatalinu {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} uppfærði atburðinn {event} í dagatalinu {calendar}", "You updated event {event} in calendar {calendar}" : "Þú uppfærðir atburðinn {event} í dagatalinu {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} bjó til verkefnið {todo} á listanum {calendar}", "You created todo {todo} in list {calendar}" : "Þú bjóst til verkefnið {todo} á listanum {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} eyddi verkefninu {todo} af listanum {calendar}", "You deleted todo {todo} from list {calendar}" : "Þú eyddir verkefninu {todo} af listanum {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} uppfærði verkefnið {todo} á listanum {calendar}", "You updated todo {todo} in list {calendar}" : "Þú uppfærðir verkefnið {todo} á listanum {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} leysti verkefnið {todo} á listanum {calendar}", "You solved todo {todo} in list {calendar}" : "Þú leystir verkefnið {todo} á listanum {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} opnaði aftur verkefnið {todo} á listanum {calendar}", "You reopened todo {todo} in list {calendar}" : "Þú opnaðir aftur verkefnið {todo} á listanum {calendar}", "A <strong>calendar</strong> was modified" : "<strong>Dagatali</strong> var breytt", "A calendar <strong>event</strong> was modified" : "<strong>Atburði</strong> dagatals var breytt", "A calendar <strong>todo</strong> was modified" : "<strong>Verkefnalista</strong> dagatals var breytt", "Contact birthdays" : "Afmælisdagar tengiliðar", "Personal" : "Einka", "Contacts" : "Tengiliðir", "Technical details" : "Tæknilegar upplýsingar", "Remote Address: %s" : "Fjartengt vistfang: %s", "Request ID: %s" : "Beiðni um auðkenni: %s" },"pluralForm" :"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);" } l10n/fr.json 0000604 00000010623 15247100403 0006612 0 ustar 00 { "translations": { "Calendar" : "Agenda", "Todos" : "Tâches", "{actor} created calendar {calendar}" : "{actor} a créé l'agenda {calendar}", "You created calendar {calendar}" : "Vous avez créé l'agenda {calendar}", "{actor} deleted calendar {calendar}" : "{actor} a supprimé l'agenda {calendar}", "You deleted calendar {calendar}" : "Vous avez supprimé l'agenda {calendar}", "{actor} updated calendar {calendar}" : "{actor} a mis à jour l'agenda {calendar}", "You updated calendar {calendar}" : "Vous avez mis à jour l'agenda {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} a partagé l'agenda {calendar} avec vous", "You shared calendar {calendar} with {user}" : "Vous avez partagé l'agenda {calendar} avec {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} a partagé l'agenda {calendar} avec {user}", "{actor} unshared calendar {calendar} from you" : "{actor} a cessé de partager l'agenda {calendar} avec vous", "You unshared calendar {calendar} from {user}" : "Vous avez cessé de partager l'agenda {calendar} avec {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} a cessé de partager l'agenda {calendar} avec {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} a cessé de partager l'agenda {calendar} avec lui-même", "You shared calendar {calendar} with group {group}" : "Vous avez partagé l'agenda {calendar} avec le groupe {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} a partagé l'agenda {calendar} avec le groupe {group}", "You unshared calendar {calendar} from group {group}" : "Vous avez cessé de partager l'agenda {calendar} avec le groupe {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} a cessé de partager l'agenda {calendar} avec le groupe {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} a créé l'évènement {event} dans l'agenda {calendar}", "You created event {event} in calendar {calendar}" : "Vous avez créé l'évènement {event} dans l'agenda {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} a supprimé l'évènement {event} de l'agenda {calendar}", "You deleted event {event} from calendar {calendar}" : "Vous avez supprimé l'évènement {event} de l'agenda {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} a mis à jour l'évènement {event} dans l'agenda {calendar}", "You updated event {event} in calendar {calendar}" : "Vous avez mis à jour l'évènement {event} dans l'agenda {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} a créé la tâche {todo} dans la liste {calendar}", "You created todo {todo} in list {calendar}" : "Vous avez créé la tâche {todo} dans la liste {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} a supprimé la tâche {todo} de la liste {calendar}", "You deleted todo {todo} from list {calendar}" : "Vous avez supprimé la tâche {todo} de la liste {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} a mis à jour la tâche {todo} dans la liste {calendar}", "You updated todo {todo} in list {calendar}" : "Vous avez mis à jour la tâche {todo} dans la liste {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} a terminé la tâche {todo} de la liste {calendar}", "You solved todo {todo} in list {calendar}" : "Vous avez terminé la tâche {todo} de la liste {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} a réouvert la tâche {todo} dans la liste {calendar}", "You reopened todo {todo} in list {calendar}" : "Vous avez réouvert la tâche {todo} dans la liste {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>agenda</strong> a été modifié", "A calendar <strong>event</strong> was modified" : "Un <strong>événement</strong> de l'agenda a été modifié", "A calendar <strong>todo</strong> was modified" : "Une <strong>liste de tâches</strong> de l'agenda a été modifiée", "Contact birthdays" : "Anniversaires des contacts", "Personal" : "Personnel", "Contacts" : "Contacts", "Technical details" : "Détails techniques", "Remote Address: %s" : "Adresse distante : %s", "Request ID: %s" : "ID de la requête : %s" },"pluralForm" :"nplurals=2; plural=(n > 1);" } l10n/es.json 0000604 00000010502 15247100403 0006606 0 ustar 00 { "translations": { "Calendar" : "Calendario", "Todos" : "Todos", "{actor} created calendar {calendar}" : "{actor} creó el calendario {calendar}", "You created calendar {calendar}" : "Usted creó el calendario {calendar}", "{actor} deleted calendar {calendar}" : "{actor} eliminó el calendario {calendar}", "You deleted calendar {calendar}" : "Usted eliminó el calendario {calendar}", "{actor} updated calendar {calendar}" : "{actor} actualizó el calendario {calendar}", "You updated calendar {calendar}" : "Usted actualizó el calendario {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} compartió el calendario {calendar} con usted", "You shared calendar {calendar} with {user}" : "Usted compartió el calendario {calendar} con {usuario}", "{actor} shared calendar {calendar} with {user}" : "{actor} compartió el calendario {calendar} con {user}", "{actor} unshared calendar {calendar} from you" : "{actor} dejó de compartir el calendario {calendar} con usted", "You unshared calendar {calendar} from {user}" : "Usted dejó de compartir el calendario {calendar} de {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} dejó de compartir el calendario {calendar} del usuario{user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} dejó de compartir su propio calendario {calendar}", "You shared calendar {calendar} with group {group}" : "Usted compartió el calendario {calendar} con el grupo {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} compartió el calendario {calendar} con el grupo {group}", "You unshared calendar {calendar} from group {group}" : "Usted dejó de compartir el calendario {calendar} del grupo {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} dejó de compartir el calendario {calendar} del grupo {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} creó el evento {event} en el calendario {calendar}", "You created event {event} in calendar {calendar}" : "Usted creó el evento {event} en el calendario {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} eliminó el evento {event} del calendario {calendar}", "You deleted event {event} from calendar {calendar}" : "Usted eliminó el evento {event} del calendario {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} actualizó el evento {event} en el calendario {calendar}", "You updated event {event} in calendar {calendar}" : "Usted actualizó el evento {event} en el calendario {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} creó la tarea {todo} en la lista {calendar}", "You created todo {todo} in list {calendar}" : "Usted creó la tarea {todo} en la lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} eliminó la tarea {todo} de la lista {calendar}", "You deleted todo {todo} from list {calendar}" : "Usted eliminó la tarea {tod} de la lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} actualizó la tarea {todo }en la lista {calendar}", "You updated todo {todo} in list {calendar}" : "Usted actualizó la tarea {todo} en la lista {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} completó la tarea {todo} en la lista {calendar}", "You solved todo {todo} in list {calendar}" : "Usted completó la tarea {todo} en la lista {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} reabrió la tarea {todo} en la lista {calendar}", "You reopened todo {todo} in list {calendar}" : "Usted reabrió la tarea {todo} en la lista {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendario</strong> fue modificado.", "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> del calendario fue modificado.", "A calendar <strong>todo</strong> was modified" : "Una <strong>lista de tareas</strong> fue modificada", "Contact birthdays" : "Cumpleaños del contacto", "Personal" : "Personal", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de la solicitud: %s" },"pluralForm" :"nplurals=2; plural=(n != 1);" } l10n/ko.js 0000604 00000010641 15247100403 0006257 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "달력", "Todos" : "할 일", "{actor} created calendar {calendar}" : "{actor} 님이 달력 {calendar}을(를) 생성함", "You created calendar {calendar}" : "달력 {calendar}을(를) 생성함", "{actor} deleted calendar {calendar}" : "{actor} 님이 달력 {calendar}을(를) 삭제함", "You deleted calendar {calendar}" : "달력 {calendar}을(를) 삭제함", "{actor} updated calendar {calendar}" : "{actor} 님이 달력 {calendar}을(를) 업데이트함", "You updated calendar {calendar}" : "달력 {calendar}을(를) 업데이트함", "{actor} shared calendar {calendar} with you" : "{actor} 님이 내게 달력 {calendar}을(를) 공유함", "You shared calendar {calendar} with {user}" : "{user} 님과 달력 {calendar}을(를) 공유함", "{actor} shared calendar {calendar} with {user}" : "{actor} 님이 {user} 님과 달력 {calendar}을(를) 공유함", "{actor} unshared calendar {calendar} from you" : "{actor} 님이 달력 {calendar}의 공유를 중단함", "You unshared calendar {calendar} from {user}" : "{user} 님과 달력 {calendar}의 공유를 중단함", "{actor} unshared calendar {calendar} from {user}" : "{actor} 님이 {user} 님과 {calendar} 달력의 공유를 중단함", "{actor} unshared calendar {calendar} from themselves" : "{actor} 님이 달력 {calendar}의 공유를 해제함", "You shared calendar {calendar} with group {group}" : "달력 {calendar}을(를) 그룹 {group}와(과) 공유함", "{actor} shared calendar {calendar} with group {group}" : "{actor} 님이 달력 {calendar}을(를) 그룹 {group}와(과) 공유함", "You unshared calendar {calendar} from group {group}" : "달력 {calendar}을(를) 그룹 {group}와(과) 공유하지 않음", "{actor} unshared calendar {calendar} from group {group}" : "{actor} 님이 달력 {calendar}을(를) 그룹 {group}와(과) 공유하지 않음", "{actor} created event {event} in calendar {calendar}" : "{actor} 님이 행사 {event}을(를) 달력 {calendar}에 생성함", "You created event {event} in calendar {calendar}" : "행사 {event}을(를) 달력 {calendar}에 생성함", "{actor} deleted event {event} from calendar {calendar}" : "{actor} 님이 행사 {event}을(를) 달력 {calendar}에서 삭제함", "You deleted event {event} from calendar {calendar}" : "행사 {event}을(를) 달력 {calendar}에서 삭제함", "{actor} updated event {event} in calendar {calendar}" : "{actor} 님이 달력 {calendar}의 행사 {event}을(를) 업데이트함", "You updated event {event} in calendar {calendar}" : "달력 {calendar}의 행사 {event}을(를) 업데이트함", "{actor} created todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}에 할 일 {todo}을(를) 생성함", "You created todo {todo} in list {calendar}" : "목록 {calendar}에 할 일 {todo}을(를) 생성함", "{actor} deleted todo {todo} from list {calendar}" : "{actor} 님이 목록 {calendar}에서 할 일 {todo}을(를) 삭제함", "You deleted todo {todo} from list {calendar}" : "목록 {calendar}에서 할 일 {todo}을(를) 삭제함", "{actor} updated todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}의 할 일 {todo}을(를) 업데이트함", "You updated todo {todo} in list {calendar}" : "목록 {calendar}의 할 일 {todo}을(를) 업데이트함", "{actor} solved todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}의 할 일 {todo}을(를) 끝냄", "You solved todo {todo} in list {calendar}" : "목록 {calendar}의 할 일 {todo}을(를) 끝냄", "{actor} reopened todo {todo} in list {calendar}" : "{actor} 님이 목록 {calendar}의 할 일 {todo}을(를) 다시 염", "You reopened todo {todo} in list {calendar}" : "목록 {calendar}의 할 일 {todo}을(를) 다시 염", "A <strong>calendar</strong> was modified" : "<strong>달력</strong>이 수정됨", "A calendar <strong>event</strong> was modified" : "달력 <strong>행사</strong>가 수정됨", "A calendar <strong>todo</strong> was modified" : "달력의 <strong>할 일</strong>이 수정됨", "Contact birthdays" : "연락처에 등록된 생일", "Personal" : "개인", "Contacts" : "연락처", "Technical details" : "기술 정보", "Remote Address: %s" : "원격 주소: %s", "Request ID: %s" : "요청 ID: %s" }, "nplurals=1; plural=0;"); l10n/zh_TW.js 0000604 00000000453 15247100403 0006701 0 ustar 00 OC.L10N.register( "dav", { "Contact birthdays" : "聯絡人生日", "Personal" : "個人", "Contacts" : "聯絡人", "Technical details" : "技術細節", "Remote Address: %s" : "遠端位置:%s", "Request ID: %s" : "請求編號:%s" }, "nplurals=1; plural=0;"); l10n/hu_HU.js 0000604 00000011120 15247100403 0006647 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Naptár", "Todos" : "Teendők", "{actor} created calendar {calendar}" : "{actor} létrehozta a naptárt: {calendar}", "You created calendar {calendar}" : "Létrehoztad a naptárt: {calendar}", "{actor} deleted calendar {calendar}" : "{actor} törölte a naptárt: {calendar}", "You deleted calendar {calendar}" : "Törölted a naptárt: {calendar}", "{actor} updated calendar {calendar}" : "{actor} frissítette a napárt: {calendar}", "You updated calendar {calendar}" : "Frissítetted a naptárt: {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} megosztotta veled ezt a naptárt: {calendar}", "You shared calendar {calendar} with {user}" : "Megosztottad ezt a napárt: {calendar} vele: {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} megosztotta ezt a napárt: {calendar} vele: {user}", "{actor} unshared calendar {calendar} from you" : "{actor} visszavonta töled a naptár megosztását: {calendar}", "You unshared calendar {calendar} from {user}" : "Visszavontad a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} visszavonta a naptár megosztását: {calendar} tőle: {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} visszavonta tőlük a naptár megosztását: {calendar}", "You shared calendar {calendar} with group {group}" : "Megosztottad ezt a naptárt: {calendar} evvel a csoporttal: {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} megosztotta ezt a naptárt: {calendar} evvel a csoporttal: {group}", "You unshared calendar {calendar} from group {group}" : "Visszavontad ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} visszavonta ennek a naptárnak a magosztását: {calendar} ettől a csoporttól: {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} létrehozta ezt az eseményt: {event} ebben a naptárban: {calendar}", "You created event {event} in calendar {calendar}" : "Létrehoztad ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} törölte ezt az eseményt: {event} ebből a naptárból: {calendar}", "You deleted event {event} from calendar {calendar}" : "Törölted ezt az eseményt: {event} ebből a naptárból: {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} frissítette ezt az eseményt: {event} ebben a naptárban: {calendar}", "You updated event {event} in calendar {calendar}" : "Frissítetted ezt az eseményt: {event} ebben a naptárban: {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} létrehozta ezt a teendőt: {todo} ebben a listában: {calendar}", "You created todo {todo} in list {calendar}" : "Létrehoztad ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} törölte ezt a teendőt: {todo} ebből a listából: {calendar}", "You deleted todo {todo} from list {calendar}" : "Törölted ezt a teendőt: {todo} ebből a listából: {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} frissítette ezt a teendőt: {todo} ebben a listában: {calendar}", "You updated todo {todo} in list {calendar}" : "Frissítetted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} elintézte ezt a teendőt: {todo} ebben a listában: {calendar}", "You solved todo {todo} in list {calendar}" : "Elintézted ezt a teendőt: {todo} ebben a listában: {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} újranyitotta ezt a teendőt: {todo} ebben a listában: {calendar}", "You reopened todo {todo} in list {calendar}" : "Újranyitottad ezt a teendőt: {todo} ebben a listában: {calendar}", "A <strong>calendar</strong> was modified" : "Egy <strong>naptár</strong> megváltozott", "A calendar <strong>event</strong> was modified" : "Egy naptár <strong>esemény</strong> megváltozott", "A calendar <strong>todo</strong> was modified" : "Egy naptár <strong>teendő</strong> megváltozott", "Contact birthdays" : "Születésnapok", "Personal" : "Személyes", "Contacts" : "Névjegyek", "Technical details" : "Technikai adatok", "Remote Address: %s" : "Távoli cím: %s", "Request ID: %s" : "Kérelem azonosító: %s" }, "nplurals=2; plural=(n != 1);"); l10n/ro.json 0000604 00000010572 15247100403 0006626 0 ustar 00 { "translations": { "Calendar" : "Calendar", "Todos" : "De făcut", "{actor} created calendar {calendar}" : "{actor} a creat calendarul {calendar}", "You created calendar {calendar}" : "Ai creat calendarul {calendar}", "{actor} deleted calendar {calendar}" : "{actor} a șters calendarul {calendar}", "You deleted calendar {calendar}" : "Ai șters calendarul {calendar}", "{actor} updated calendar {calendar}" : "{actor} a actualiza calendarul {calendar}", "You updated calendar {calendar}" : "Ai actualizat calendarul {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} a partajat calendarul {calendar} cu tine", "You shared calendar {calendar} with {user}" : "Ai partajat calendarul {calendar} cu {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} a partajat calendarul {calendar} cu {user}", "{actor} unshared calendar {calendar} from you" : "{actor} a eliminat partajarea calendarului {calendar} cu tine", "You unshared calendar {calendar} from {user}" : "Ai eliminat partajarea calendarului {calendar} cu {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} a eliminat partajarea calendarului {calendar} cu {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} a eliminat partajarea calendarului {calendar} catre sine", "You shared calendar {calendar} with group {group}" : "Ai partajat calendarul {calendar} cu grupul {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} a partajat calendarul {calendar} cu grupul {group}", "You unshared calendar {calendar} from group {group}" : "Ai eliminat partajarea calendarului {calendar} către grupul {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} a elimina partajarea calendarului {calendar} către grupul {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} a creat evenimentul {event} în calendarul {calendar}", "You created event {event} in calendar {calendar}" : "Ai creat evenimentul {event} în calendarul {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} a șters evenimentul {event} din calendarul {calendar}", "You deleted event {event} from calendar {calendar}" : "AI șters evenimentul {event} din calendarul {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} a actualizat evenimentul {event} din calendarul {calendar}", "You updated event {event} in calendar {calendar}" : "Ai actualizat evenimentul {event} din calendarul {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} a creat lista {todo} în calendarul {calendar}", "You created todo {todo} in list {calendar}" : "Ai creat lista {todo} în lista {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} a șters lista {todo} din calendarul {calendar}", "You deleted todo {todo} from list {calendar}" : "Ai șters lista {todo} din calendarul {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} a actualizat lista {todo} din calendarul {calendar}", "You updated todo {todo} in list {calendar}" : "Ai actualizat lista {todo} din calendarul {calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} a completat lista {todo} din calendarul {calendar}", "You solved todo {todo} in list {calendar}" : "Ai completat lista {todo} din calendarul {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} a redeschis lista {todo} din calendarul {calendar}", "You reopened todo {todo} in list {calendar}" : "Ai redeschis lista {todo} din calendarul {calendar}", "A <strong>calendar</strong> was modified" : "Un <strong>calendar</strong> a fost modificat", "A calendar <strong>event</strong> was modified" : "Un <strong>eveniment</strong> din calendar a fost modificat", "A calendar <strong>todo</strong> was modified" : "O <strong>listă</strong> din calendar a fost modificată", "Contact birthdays" : "Zile de naștere ale persoanelor de contact", "Personal" : "Personal", "Contacts" : "Persoane de contact", "Technical details" : "Detalii tehnice", "Remote Address: %s" : "Adresă la distanță: %s", "Request ID: %s" : "ID-ul cererii: %s" },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } l10n/sq.js 0000604 00000010200 15247100403 0006260 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "Kalendar", "Todos" : "Për tu bërë", "{actor} created calendar {calendar}" : "{aktori} krijoi kalendarin {kalendarin}", "You created calendar {calendar}" : "Ju krijuat kalendarin {calendar}", "{actor} deleted calendar {calendar}" : "{actor} fshiu kalendarin {calendar}", "You deleted calendar {calendar}" : "Ju fshit kalendarin {calendar}", "{actor} updated calendar {calendar}" : "{actor} përditësoi kalendarin {calendar}", "You updated calendar {calendar}" : "Ju përditësuat kalendarin {calendar}", "{actor} shared calendar {calendar} with you" : " {actor} ndau kalendarin {calendar} me ju", "You shared calendar {calendar} with {user}" : "Ju ndat kalendarin {calendar} me {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} ndau kalendarin {calendar} me {user}", "{actor} unshared calendar {calendar} from you" : "{actor} ndaloj së ndari kalendarin {calendar} me ju", "You unshared calendar {calendar} from {user}" : "Ju ndaluat së ndari kalendarin {calendar} me {user}", "{actor} unshared calendar {calendar} from {user}" : "{actor} ndaloj së ndari kalendarin {calendar} me {user}", "{actor} unshared calendar {calendar} from themselves" : "{actor} ndaloj së ndari kalendarin {calendar} me veten", "You shared calendar {calendar} with group {group}" : "Ju ndat kalendarin {calendar} me grupin {group}", "{actor} shared calendar {calendar} with group {group}" : "{actor} ndau kalendarin {calendar} me grupin {group}", "You unshared calendar {calendar} from group {group}" : "Ju ndaluat së ndari kalendarin {calendar} me grupin {group}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} ndaloj së ndari kalendarin {calendar} me grupin {group}", "{actor} created event {event} in calendar {calendar}" : "{actor} krijoj eventin {event} në kalendarin {calendar}", "You created event {event} in calendar {calendar}" : "Ju krijuat eventin {event} në kalendarin {calendar}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} fshiu eventin {event} nga kalendari {calendar}", "You deleted event {event} from calendar {calendar}" : "Ju fshit eventin {event} nga kalndari {calendar}", "{actor} updated event {event} in calendar {calendar}" : "{actor} përditsoi eventin {event} në kalndarin {calendar}", "You updated event {event} in calendar {calendar}" : "Ju përditësuat eventin {event} në kalndarin {calendar}", "{actor} created todo {todo} in list {calendar}" : "{actor} u krijua todo{todo} në listën {calendar}", "You created todo {todo} in list {calendar}" : "Ju krijuat todo {todo} në listën {calendar}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} u fshi todo{ todo} nga lista{calendar}", "You deleted todo {todo} from list {calendar}" : "Ju fshit todo{todo} nga lista {calendar}", "{actor} updated todo {todo} in list {calendar}" : "{actor} u përditësua todo{todo} në listën{calendar}", "You updated todo {todo} in list {calendar}" : "Ju përditësuat përtëbërë {todo} në listën{calendar}", "{actor} solved todo {todo} in list {calendar}" : "{actor} zgjidhi përtëbërë {todo} në listën {calendar}", "You solved todo {todo} in list {calendar}" : "Ju zgjidhët përtëbërë {todo} në listën {calendar}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} rihapi përtëbërë {todo} në listën {calendar}", "You reopened todo {todo} in list {calendar}" : "Ju rihapët përtëbërë {todo} në listën {calendar}", "A <strong>calendar</strong> was modified" : "Një <strong>kalendar</strong> u modifikua", "A calendar <strong>event</strong> was modified" : "Një <strong>event</strong> në kalendar u modifikua", "A calendar <strong>todo</strong> was modified" : "Një kalendar <strong>todo<strong> u modifikua", "Contact birthdays" : "Ditëlindjet e kontakteve", "Personal" : "Personale", "Contacts" : "Kontaktet", "Technical details" : "Detaje teknike", "Remote Address: %s" : "Adresa remote: %s", "Request ID: %s" : "ID e kërkesës: %s" }, "nplurals=2; plural=(n != 1);"); l10n/.gitkeep 0000604 00000000000 15247100403 0006725 0 ustar 00 l10n/zh_CN.js 0000604 00000010061 15247100403 0006643 0 ustar 00 OC.L10N.register( "dav", { "Calendar" : "日历", "Todos" : "待办事项", "{actor} created calendar {calendar}" : "{actor} 创建了日历 {calendar}", "You created calendar {calendar}" : "您创建的日历 {calendar}", "{actor} deleted calendar {calendar}" : "{actor} 删除了日历 {calendar}", "You deleted calendar {calendar}" : "您删除的日历 {calendar}", "{actor} updated calendar {calendar}" : "{actor} 更新了日历 {calendar}", "You updated calendar {calendar}" : "您更新了日历 {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} 收到的日历分享 {calendar}", "You shared calendar {calendar} with {user}" : "您与 {user} 分享了日历 {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} 与 {user} 分享了日历 {calendar}", "{actor} unshared calendar {calendar} from you" : "{actor} 取消分享 {calendar} 给您", "You unshared calendar {calendar} from {user}" : "您取消分享给 {user} 的日历 {calendar}", "{actor} unshared calendar {calendar} from {user}" : "{actor} 取消分享给 {uesr} 的日历 {calendar}", "{actor} unshared calendar {calendar} from themselves" : "{actor} 取消分享日历 {calendar}", "You shared calendar {calendar} with group {group}" : "您通过组 {group} 分享了日历 {calendar}", "{actor} shared calendar {calendar} with group {group}" : "{actor} 通过组 {group} 分享了日历 {calendar}", "You unshared calendar {calendar} from group {group}" : "您取消分享给组 {group} 的日历 {calendar}", "{actor} unshared calendar {calendar} from group {group}" : "{actor} 取消分享给组 {group} 的日历 {calendar}", "{actor} created event {event} in calendar {calendar}" : "{actor} 在日历 {calendar} 中创建了事件 {event}", "You created event {event} in calendar {calendar}" : "您在日历 {calendar} 中创建了事件 {event}", "{actor} deleted event {event} from calendar {calendar}" : "{actor} 在日历 {calendar} 中删除了事件 {event}", "You deleted event {event} from calendar {calendar}" : "您在日历 {calendar} 中删除了事件 {event}", "{actor} updated event {event} in calendar {calendar}" : "{actor} 在日历 {calendar} 中更新了事件 {event}", "You updated event {event} in calendar {calendar}" : "您在日历 {calendar} 中更新了事件 {event}", "{actor} created todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中创建了待办事项 {todo}", "You created todo {todo} in list {calendar}" : "您在列表 {calendar} 中创建了待办事项 {todo}", "{actor} deleted todo {todo} from list {calendar}" : "{actor} 在列表 {calendar} 中删除了待办事项 {todo}", "You deleted todo {todo} from list {calendar}" : "您在列表 {calendar} 中删除了待办事项 {todo}", "{actor} updated todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中更新了待办事项 {todo}", "You updated todo {todo} in list {calendar}" : "您在列表 {calendar} 中更新了待办事项 {todo}", "{actor} solved todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中解决了待办事项 {todo}", "You solved todo {todo} in list {calendar}" : "您在列表 {calendar} 中解决了待办事项 {todo}", "{actor} reopened todo {todo} in list {calendar}" : "{actor} 在列表 {calendar} 中重新打开了待办事项 {todo}", "You reopened todo {todo} in list {calendar}" : "您在列表 {calendar} 中重新打开了待办事项 {todo}", "A <strong>calendar</strong> was modified" : "<strong>日历</strong>已经修改", "A calendar <strong>event</strong> was modified" : "日历中<strong>事件</strong>已经修改", "A calendar <strong>todo</strong> was modified" : "列表中<strong>待办事项</strong>已经修改", "Contact birthdays" : "联系人生日", "Personal" : "个人", "Contacts" : "联系人", "Technical details" : "技术细节", "Remote Address: %s" : "远程地址: %s", "Request ID: %s" : "请求 ID: %s" }, "nplurals=1; plural=0;"); lib/Upload/UploadHome.php 0000604 00000004623 15247100403 0011301 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Upload; use OC\Files\Filesystem; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; class UploadHome implements ICollection { /** * UploadHome constructor. * * @param array $principalInfo */ public function __construct($principalInfo) { $this->principalInfo = $principalInfo; } function createFile($name, $data = null) { throw new Forbidden('Permission denied to create file (filename ' . $name . ')'); } function createDirectory($name) { $this->impl()->createDirectory($name); } function getChild($name) { return new UploadFolder($this->impl()->getChild($name)); } function getChildren() { return array_map(function($node) { return new UploadFolder($node); }, $this->impl()->getChildren()); } function childExists($name) { return !is_null($this->getChild($name)); } function delete() { $this->impl()->delete(); } function getName() { return 'uploads'; } function setName($name) { throw new Forbidden('Permission denied to rename this folder'); } function getLastModified() { return $this->impl()->getLastModified(); } /** * @return Directory */ private function impl() { $rootView = new View(); $user = \OC::$server->getUserSession()->getUser(); Filesystem::initMountPoints($user->getUID()); if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) { $rootView->mkdir('/' . $user->getUID() . '/uploads'); } $view = new View('/' . $user->getUID() . '/uploads'); $rootInfo = $view->getFileInfo(''); $impl = new Directory($view, $rootInfo); return $impl; } } lib/Upload/UploadFolder.php 0000604 00000003755 15247100403 0011631 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Upload; use OCA\DAV\Connector\Sabre\Directory; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; class UploadFolder implements ICollection { private $node; function __construct(Directory $node) { $this->node = $node; } function createFile($name, $data = null) { // TODO: verify name - should be a simple number $this->node->createFile($name, $data); } function createDirectory($name) { throw new Forbidden('Permission denied to create file (filename ' . $name . ')'); } function getChild($name) { if ($name === '.file') { return new FutureFile($this->node, '.file'); } return $this->node->getChild($name); } function getChildren() { $children = $this->node->getChildren(); $children[] = new FutureFile($this->node, '.file'); return $children; } function childExists($name) { if ($name === '.file') { return true; } return $this->node->childExists($name); } function delete() { $this->node->delete(); } function getName() { return $this->node->getName(); } function setName($name) { throw new Forbidden('Permission denied to rename this folder'); } function getLastModified() { return $this->node->getLastModified(); } } lib/Upload/FutureFile.php 0000604 00000004617 15247100403 0011321 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Upload; use OCA\DAV\Connector\Sabre\Directory; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\IFile; /** * Class FutureFile * * The FutureFile is a SabreDav IFile which connects the chunked upload directory * with the AssemblyStream, who does the final assembly job * * @package OCA\DAV\Upload */ class FutureFile implements \Sabre\DAV\IFile { /** @var Directory */ private $root; /** @var string */ private $name; /** * @param Directory $root * @param string $name */ function __construct(Directory $root, $name) { $this->root = $root; $this->name = $name; } /** * @inheritdoc */ function put($data) { throw new Forbidden('Permission denied to put into this file'); } /** * @inheritdoc */ function get() { $nodes = $this->root->getChildren(); return AssemblyStream::wrap($nodes); } /** * @inheritdoc */ function getContentType() { return 'application/octet-stream'; } /** * @inheritdoc */ function getETag() { return $this->root->getETag(); } /** * @inheritdoc */ function getSize() { $children = $this->root->getChildren(); $sizes = array_map(function($node) { /** @var IFile $node */ return $node->getSize(); }, $children); return array_sum($sizes); } /** * @inheritdoc */ function delete() { $this->root->delete(); } /** * @inheritdoc */ function getName() { return $this->name; } /** * @inheritdoc */ function setName($name) { throw new Forbidden('Permission denied to rename this file'); } /** * @inheritdoc */ function getLastModified() { return $this->root->getLastModified(); } } lib/Upload/AssemblyStream.php 0000604 00000013622 15247100403 0012176 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Upload; use Sabre\DAV\IFile; /** * Class AssemblyStream * * The assembly stream is a virtual stream that wraps multiple chunks. * Reading from the stream transparently accessed the underlying chunks and * give a representation as if they were already merged together. * * @package OCA\DAV\Upload */ class AssemblyStream implements \Icewind\Streams\File { /** @var resource */ private $context; /** @var IFile[] */ private $nodes; /** @var int */ private $pos = 0; /** @var array */ private $sortedNodes; /** @var int */ private $size; /** @var resource */ private $currentStream = null; /** * @param string $path * @param string $mode * @param int $options * @param string &$opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path) { $this->loadContext('assembly'); // sort the nodes $nodes = $this->nodes; // http://stackoverflow.com/a/10985500 @usort($nodes, function(IFile $a, IFile $b) { return strnatcmp($a->getName(), $b->getName()); }); $this->nodes = $nodes; // build additional information $this->sortedNodes = []; $start = 0; foreach($this->nodes as $node) { $size = $node->getSize(); $name = $node->getName(); $this->sortedNodes[$name] = ['node' => $node, 'start' => $start, 'end' => $start + $size]; $start += $size; $this->size = $start; } return true; } /** * @param string $offset * @param int $whence * @return bool */ public function stream_seek($offset, $whence = SEEK_SET) { return false; } /** * @return int */ public function stream_tell() { return $this->pos; } /** * @param int $count * @return string */ public function stream_read($count) { do { if ($this->currentStream === null) { list($node, $posInNode) = $this->getNodeForPosition($this->pos); if (is_null($node)) { // reached last node, no more data return ''; } $this->currentStream = $this->getStream($node); fseek($this->currentStream, $posInNode); } $data = fread($this->currentStream, $count); // isset is faster than strlen if (isset($data[$count - 1])) { // we read the full count $read = $count; } else { // reaching end of stream, which happens less often so strlen is ok $read = strlen($data); } if (feof($this->currentStream)) { fclose($this->currentStream); $this->currentNode = null; $this->currentStream = null; } // if no data read, try again with the next node because // returning empty data can make the caller think there is no more // data left to read } while ($read === 0); // update position $this->pos += $read; return $data; } /** * @param string $data * @return int */ public function stream_write($data) { return false; } /** * @param int $option * @param int $arg1 * @param int $arg2 * @return bool */ public function stream_set_option($option, $arg1, $arg2) { return false; } /** * @param int $size * @return bool */ public function stream_truncate($size) { return false; } /** * @return array */ public function stream_stat() { return []; } /** * @param int $operation * @return bool */ public function stream_lock($operation) { return false; } /** * @return bool */ public function stream_flush() { return false; } /** * @return bool */ public function stream_eof() { return $this->pos >= $this->size; } /** * @return bool */ public function stream_close() { return true; } /** * Load the source from the stream context and return the context options * * @param string $name * @return array * @throws \Exception */ protected function loadContext($name) { $context = stream_context_get_options($this->context); if (isset($context[$name])) { $context = $context[$name]; } else { throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); } if (isset($context['nodes']) and is_array($context['nodes'])) { $this->nodes = $context['nodes']; } else { throw new \BadMethodCallException('Invalid context, nodes not set'); } return $context; } /** * @param IFile[] $nodes * @return resource * * @throws \BadMethodCallException */ public static function wrap(array $nodes) { $context = stream_context_create([ 'assembly' => [ 'nodes' => $nodes] ]); stream_wrapper_register('assembly', '\OCA\DAV\Upload\AssemblyStream'); try { $wrapped = fopen('assembly://', 'r', null, $context); } catch (\BadMethodCallException $e) { stream_wrapper_unregister('assembly'); throw $e; } stream_wrapper_unregister('assembly'); return $wrapped; } /** * @param $pos * @return IFile | null */ private function getNodeForPosition($pos) { foreach($this->sortedNodes as $node) { if ($pos >= $node['start'] && $pos < $node['end']) { return [$node['node'], $pos - $node['start']]; } } return null; } /** * @param IFile $node * @return resource */ private function getStream(IFile $node) { $data = $node->get(); if (is_resource($data)) { return $data; } return fopen('data://text/plain,' . $data,'r'); } } lib/Upload/RootCollection.php 0000604 00000002163 15247100403 0012200 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Upload; use Sabre\DAVACL\AbstractPrincipalCollection; class RootCollection extends AbstractPrincipalCollection { /** * @inheritdoc */ function getChildForPrincipal(array $principalInfo) { return new UploadHome($principalInfo); } /** * @inheritdoc */ function getName() { return 'uploads'; } } lib/HookManager.php 0000604 00000007671 15247100403 0010221 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\SyncService; use OCP\IUser; use OCP\IUserManager; use OCP\Util; use Symfony\Component\EventDispatcher\EventDispatcher; class HookManager { /** @var IUserManager */ private $userManager; /** @var SyncService */ private $syncService; /** @var IUser[] */ private $usersToDelete; /** @var CalDavBackend */ private $calDav; /** @var CardDavBackend */ private $cardDav; /** @var array */ private $calendarsToDelete; /** @var array */ private $addressBooksToDelete; /** @var EventDispatcher */ private $eventDispatcher; public function __construct(IUserManager $userManager, SyncService $syncService, CalDavBackend $calDav, CardDavBackend $cardDav, EventDispatcher $eventDispatcher) { $this->userManager = $userManager; $this->syncService = $syncService; $this->calDav = $calDav; $this->cardDav = $cardDav; $this->eventDispatcher = $eventDispatcher; } public function setup() { Util::connectHook('OC_User', 'post_createUser', $this, 'postCreateUser'); Util::connectHook('OC_User', 'pre_deleteUser', $this, 'preDeleteUser'); Util::connectHook('OC_User', 'post_deleteUser', $this, 'postDeleteUser'); Util::connectHook('OC_User', 'changeUser', $this, 'changeUser'); } public function postCreateUser($params) { $user = $this->userManager->get($params['uid']); $this->syncService->updateUser($user); } public function preDeleteUser($params) { $uid = $params['uid']; $this->usersToDelete[$uid] = $this->userManager->get($uid); $this->calendarsToDelete = $this->calDav->getUsersOwnCalendars('principals/users/' . $uid); $this->addressBooksToDelete = $this->cardDav->getUsersOwnAddressBooks('principals/users/' . $uid); } public function postDeleteUser($params) { $uid = $params['uid']; if (isset($this->usersToDelete[$uid])){ $this->syncService->deleteUser($this->usersToDelete[$uid]); } foreach ($this->calendarsToDelete as $calendar) { $this->calDav->deleteCalendar($calendar['id']); } $this->calDav->deleteAllSharesByUser('principals/users/' . $uid); foreach ($this->addressBooksToDelete as $addressBook) { $this->cardDav->deleteAddressBook($addressBook['id']); } } public function changeUser($params) { $user = $params['user']; $this->syncService->updateUser($user); } public function firstLogin(IUser $user = null) { if (!is_null($user)) { $principal = 'principals/users/' . $user->getUID(); if ($this->calDav->getCalendarsForUserCount($principal) === 0) { try { $this->calDav->createCalendar($principal, CalDavBackend::PERSONAL_CALENDAR_URI, [ '{DAV:}displayname' => CalDavBackend::PERSONAL_CALENDAR_NAME, ]); } catch (\Exception $ex) { \OC::$server->getLogger()->logException($ex); } } if ($this->cardDav->getAddressBooksForUserCount($principal) === 0) { try { $this->cardDav->createAddressBook($principal, CardDavBackend::PERSONAL_ADDRESSBOOK_URI, [ '{DAV:}displayname' => CardDavBackend::PERSONAL_ADDRESSBOOK_NAME, ]); } catch (\Exception $ex) { \OC::$server->getLogger()->logException($ex); } } } } } lib/Avatars/AvatarNode.php 0000604 00000004055 15247100403 0011444 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud GmbH * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Avatars; use OCP\IAvatar; use Sabre\DAV\File; class AvatarNode extends File { private $ext; private $size; private $avatar; /** * AvatarNode constructor. * * @param integer $size * @param string $ext * @param IAvatar $avatar */ public function __construct($size, $ext, $avatar) { $this->size = $size; $this->ext = $ext; $this->avatar = $avatar; } /** * Returns the name of the node. * * This is used to generate the url. * * @return string */ public function getName() { return "$this->size.$this->ext"; } public function get() { $image = $this->avatar->get($this->size); $res = $image->resource(); ob_start(); if ($this->ext === 'png') { imagepng($res); } else { imagejpeg($res); } return ob_get_clean(); } /** * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream * * @return string|null */ public function getContentType() { if ($this->ext === 'png') { return 'image/png'; } return 'image/jpeg'; } public function getETag() { return $this->avatar->getFile($this->size)->getEtag(); } public function getLastModified() { $timestamp = $this->avatar->getFile($this->size)->getMTime(); if (!empty($timestamp)) { return (int)$timestamp; } return $timestamp; } } lib/Avatars/AvatarHome.php 0000604 00000005730 15247100403 0011450 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud GmbH * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Avatars; use OCP\IAvatarManager; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; use Sabre\Uri; class AvatarHome implements ICollection { /** @var array */ private $principalInfo; /** @var IAvatarManager */ private $avatarManager; /** * AvatarHome constructor. * * @param array $principalInfo * @param IAvatarManager $avatarManager */ public function __construct($principalInfo, IAvatarManager $avatarManager) { $this->principalInfo = $principalInfo; $this->avatarManager = $avatarManager; } public function createFile($name, $data = null) { throw new Forbidden('Permission denied to create a file'); } public function createDirectory($name) { throw new Forbidden('Permission denied to create a folder'); } public function getChild($name) { $elements = pathinfo($name); $ext = isset($elements['extension']) ? $elements['extension'] : ''; $size = (int)(isset($elements['filename']) ? $elements['filename'] : '64'); if (!in_array($ext, ['jpeg', 'png'], true)) { throw new MethodNotAllowed('File format not allowed'); } if ($size <= 0 || $size > 1024) { throw new MethodNotAllowed('Invalid image size'); } $avatar = $this->avatarManager->getAvatar($this->getName()); if ($avatar === null || !$avatar->exists()) { throw new NotFound(); } return new AvatarNode($size, $ext, $avatar); } public function getChildren() { try { return [ $this->getChild('96.jpeg') ]; } catch(NotFound $exception) { return []; } } public function childExists($name) { try { $ret = $this->getChild($name); return $ret !== null; } catch (NotFound $ex) { return false; } catch (MethodNotAllowed $ex) { return false; } } public function delete() { throw new Forbidden('Permission denied to delete this folder'); } public function getName() { list(,$name) = Uri\split($this->principalInfo['uri']); return $name; } public function setName($name) { throw new Forbidden('Permission denied to rename this folder'); } /** * Returns the last modification time, as a unix timestamp * * @return int|null */ public function getLastModified() { return null; } } lib/Avatars/RootCollection.php 0000604 00000001251 15247100403 0012352 0 ustar 00 <?php namespace OCA\DAV\Avatars; use Sabre\DAVACL\AbstractPrincipalCollection; class RootCollection extends AbstractPrincipalCollection { /** * This method returns a node for a principal. * * The passed array contains principal information, and is guaranteed to * at least contain a uri item. Other properties may or may not be * supplied by the authentication backend. * * @param array $principalInfo * @return AvatarHome */ public function getChildForPrincipal(array $principalInfo) { $avatarManager = \OC::$server->getAvatarManager(); return new AvatarHome($principalInfo, $avatarManager); } public function getName() { return 'avatars'; } } lib/RootCollection.php 0000604 00000011555 15247100403 0010761 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarRoot; use OCA\DAV\CalDAV\PublicCalendarRoot; use OCA\DAV\CardDAV\AddressBookRoot; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\GroupPrincipalBackend; use OCA\DAV\DAV\SystemPrincipalBackend; use Sabre\CalDAV\Principal\Collection; use Sabre\DAV\SimpleCollection; class RootCollection extends SimpleCollection { public function __construct() { $config = \OC::$server->getConfig(); $random = \OC::$server->getSecureRandom(); $userManager = \OC::$server->getUserManager(); $db = \OC::$server->getDatabaseConnection(); $dispatcher = \OC::$server->getEventDispatcher(); $userPrincipalBackend = new Principal( $userManager, \OC::$server->getGroupManager() ); $groupPrincipalBackend = new GroupPrincipalBackend( \OC::$server->getGroupManager() ); // as soon as debug mode is enabled we allow listing of principals $disableListing = !$config->getSystemValue('debug', false); // setup the first level of the dav tree $userPrincipals = new Collection($userPrincipalBackend, 'principals/users'); $userPrincipals->disableListing = $disableListing; $groupPrincipals = new Collection($groupPrincipalBackend, 'principals/groups'); $groupPrincipals->disableListing = $disableListing; $systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system'); $systemPrincipals->disableListing = $disableListing; $filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users'); $filesCollection->disableListing = $disableListing; $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $random, $dispatcher); $calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; $publicCalendarRoot = new PublicCalendarRoot($caldavBackend); $publicCalendarRoot->disableListing = $disableListing; $systemTagCollection = new SystemTag\SystemTagsByIdCollection( \OC::$server->getSystemTagManager(), \OC::$server->getUserSession(), \OC::$server->getGroupManager() ); $systemTagRelationsCollection = new SystemTag\SystemTagsRelationsCollection( \OC::$server->getSystemTagManager(), \OC::$server->getSystemTagObjectMapper(), \OC::$server->getUserSession(), \OC::$server->getGroupManager(), \OC::$server->getEventDispatcher() ); $commentsCollection = new Comments\RootCollection( \OC::$server->getCommentsManager(), \OC::$server->getUserManager(), \OC::$server->getUserSession(), \OC::$server->getEventDispatcher(), \OC::$server->getLogger() ); $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getUserManager(), $dispatcher); $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users'); $usersAddressBookRoot->disableListing = $disableListing; $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getUserManager(), $dispatcher); $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system'); $systemAddressBookRoot->disableListing = $disableListing; $uploadCollection = new Upload\RootCollection($userPrincipalBackend, 'principals/users'); $uploadCollection->disableListing = $disableListing; $avatarCollection = new Avatars\RootCollection($userPrincipalBackend, 'principals/users'); $avatarCollection->disableListing = $disableListing; $children = [ new SimpleCollection('principals', [ $userPrincipals, $groupPrincipals, $systemPrincipals]), $filesCollection, $calendarRoot, $publicCalendarRoot, new SimpleCollection('addressbooks', [ $usersAddressBookRoot, $systemAddressBookRoot]), $systemTagCollection, $systemTagRelationsCollection, $commentsCollection, $uploadCollection, $avatarCollection ]; parent::__construct('root', $children); } } lib/Server.php 0000604 00000020720 15247100403 0007262 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Christoph Wurst <christoph@owncloud.com> * @author Georg Ehrke <georg@owncloud.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV; use OC\AppFramework\Utility\TimeFactory; use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCA\DAV\CardDAV\ImageExportPlugin; use OCA\DAV\CardDAV\PhotoCache; use OCA\DAV\Comments\CommentsPlugin; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\Connector\Sabre\BearerAuth; use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; use OCA\DAV\Connector\Sabre\CommentPropertiesPlugin; use OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin; use OCA\DAV\Connector\Sabre\DavAclPlugin; use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin; use OCA\DAV\Connector\Sabre\FakeLockerPlugin; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\FilesReportPlugin; use OCA\DAV\Connector\Sabre\SharesPlugin; use OCA\DAV\DAV\PublicAuth; use OCA\DAV\DAV\CustomPropertiesBackend; use OCA\DAV\Connector\Sabre\QuotaPlugin; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\SystemTag\SystemTagPlugin; use OCP\IRequest; use OCP\SabrePluginEvent; use Sabre\CardDAV\VCFExportPlugin; use Sabre\DAV\Auth\Plugin; use OCA\DAV\Connector\Sabre\TagsPlugin; use Sabre\HTTP\Auth\Bearer; use SearchDAV\DAV\SearchPlugin; class Server { /** @var IRequest */ private $request; /** @var string */ private $baseUri; /** @var Connector\Sabre\Server */ private $server; public function __construct(IRequest $request, $baseUri) { $this->request = $request; $this->baseUri = $baseUri; $logger = \OC::$server->getLogger(); $mailer = \OC::$server->getMailer(); $dispatcher = \OC::$server->getEventDispatcher(); $timezone = new TimeFactory(); $root = new RootCollection(); $this->server = new \OCA\DAV\Connector\Sabre\Server($root); // Add maintenance plugin $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin(\OC::$server->getConfig())); // Backends $authBackend = new Auth( \OC::$server->getSession(), \OC::$server->getUserSession(), \OC::$server->getRequest(), \OC::$server->getTwoFactorAuthManager(), \OC::$server->getBruteForceThrottler() ); // Set URL explicitly due to reverse-proxy situations $this->server->httpRequest->setUrl($this->request->getRequestUri()); $this->server->setBaseUri($this->baseUri); $this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig())); $authPlugin = new Plugin(); $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); // allow setup of additional auth backends $event = new SabrePluginEvent($this->server); $dispatcher->dispatch('OCA\DAV\Connector\Sabre::authInit', $event); $bearerAuthBackend = new BearerAuth( \OC::$server->getUserSession(), \OC::$server->getSession(), \OC::$server->getRequest() ); $authPlugin->addBackend($bearerAuthBackend); // because we are throwing exceptions this plugin has to be the last one $authPlugin->addBackend($authBackend); // debugging if(\OC::$server->getConfig()->getSystemValue('debug', false)) { $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin()); } else { $this->server->addPlugin(new DummyGetResponsePlugin()); } $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $logger)); $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin()); $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); // acl $acl = new DavAclPlugin(); $acl->principalCollectionSet = [ 'principals/users', 'principals/groups' ]; $acl->defaultUsernamePath = 'principals/users'; $this->server->addPlugin($acl); // calendar plugins $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin()); $this->server->addPlugin(new IMipPlugin($mailer, $logger, $timezone)); $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); $this->server->addPlugin(new \OCA\DAV\CalDAV\Publishing\PublishPlugin( \OC::$server->getConfig(), \OC::$server->getURLGenerator() )); // addressbook plugins $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); $this->server->addPlugin(new VCFExportPlugin()); $this->server->addPlugin(new ImageExportPlugin(new PhotoCache(\OC::$server->getAppDataDir('dav-photocache')))); // system tags plugins $this->server->addPlugin(new SystemTagPlugin( \OC::$server->getSystemTagManager(), \OC::$server->getGroupManager(), \OC::$server->getUserSession() )); // comments plugin $this->server->addPlugin(new CommentsPlugin( \OC::$server->getCommentsManager(), \OC::$server->getUserSession() )); $this->server->addPlugin(new CopyEtagHeaderPlugin()); // allow setup of additional plugins $dispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event); // Some WebDAV clients do require Class 2 WebDAV support (locking), since // we do not provide locking we emulate it using a fake locking plugin. if($request->isUserAgent([ '/WebDAVFS/', '/Microsoft Office OneNote 2013/', '/^Microsoft-WebDAV/',// Microsoft-WebDAV-MiniRedir/6.1.7601 ])) { $this->server->addPlugin(new FakeLockerPlugin()); } if (BrowserErrorPagePlugin::isBrowserRequest($request)) { $this->server->addPlugin(new BrowserErrorPagePlugin()); } // wait with registering these until auth is handled and the filesystem is setup $this->server->on('beforeMethod', function () { // custom properties plugin must be the last one $userSession = \OC::$server->getUserSession(); $user = $userSession->getUser(); if ($user !== null) { $view = \OC\Files\Filesystem::getView(); $this->server->addPlugin( new FilesPlugin( $this->server->tree, \OC::$server->getConfig(), $this->request, \OC::$server->getPreviewManager(), false, !\OC::$server->getConfig()->getSystemValue('debug', false) ) ); $this->server->addPlugin( new \Sabre\DAV\PropertyStorage\Plugin( new CustomPropertiesBackend( $this->server->tree, \OC::$server->getDatabaseConnection(), \OC::$server->getUserSession()->getUser() ) ) ); if ($view !== null) { $this->server->addPlugin( new QuotaPlugin($view)); } $this->server->addPlugin( new TagsPlugin( $this->server->tree, \OC::$server->getTagManager() ) ); // TODO: switch to LazyUserFolder $userFolder = \OC::$server->getUserFolder(); $this->server->addPlugin(new SharesPlugin( $this->server->tree, $userSession, $userFolder, \OC::$server->getShareManager() )); $this->server->addPlugin(new CommentPropertiesPlugin( \OC::$server->getCommentsManager(), $userSession )); $this->server->addPlugin(new \OCA\DAV\CalDAV\Search\SearchPlugin()); if ($view !== null) { $this->server->addPlugin(new FilesReportPlugin( $this->server->tree, $view, \OC::$server->getSystemTagManager(), \OC::$server->getSystemTagObjectMapper(), \OC::$server->getTagManager(), $userSession, \OC::$server->getGroupManager(), $userFolder )); $this->server->addPlugin(new SearchPlugin(new \OCA\DAV\Files\FileSearchBackend( $this->server->tree, $user, \OC::$server->getRootFolder(), \OC::$server->getShareManager(), $view ))); } } }); } public function exec() { $this->server->exec(); } } lib/CalDAV/CalendarObject.php 0000604 00000004645 15247100403 0011716 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017, Georg Ehrke * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\VObject\Component; use Sabre\VObject\Property; use Sabre\VObject\Reader; class CalendarObject extends \Sabre\CalDAV\CalendarObject { /** * @inheritdoc */ function get() { $data = parent::get(); if ($this->isShared() && $this->objectData['classification'] === CalDavBackend::CLASSIFICATION_CONFIDENTIAL) { return $this->createConfidentialObject($data); } return $data; } protected function isShared() { if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return false; } return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']; } /** * @param string $calData * @return string */ private static function createConfidentialObject($calData) { $vObject = Reader::read($calData); /** @var Component $vElement */ $vElement = null; if(isset($vObject->VEVENT)) { $vElement = $vObject->VEVENT; } if(isset($vObject->VJOURNAL)) { $vElement = $vObject->VJOURNAL; } if(isset($vObject->VTODO)) { $vElement = $vObject->VTODO; } if(!is_null($vElement)) { foreach ($vElement->children() as &$property) { /** @var Property $property */ switch($property->name) { case 'CREATED': case 'DTSTART': case 'RRULE': case 'DURATION': case 'DTEND': case 'CLASS': case 'UID': break; case 'SUMMARY': $property->setValue('Busy'); break; default: $vElement->__unset($property->name); unset($property); break; } } } return $vObject->serialize(); } } lib/CalDAV/BirthdayService.php 0000604 00000021471 15247100403 0012141 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2016, Georg Ehrke * * @author Achim Königs <garfonso@tratschtante.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <georg@nextcloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Exception; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\DAV\GroupPrincipalBackend; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VCard; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Document; use Sabre\VObject\InvalidDataException; use Sabre\VObject\Property\VCard\DateAndOrTime; use Sabre\VObject\Reader; class BirthdayService { const BIRTHDAY_CALENDAR_URI = 'contact_birthdays'; /** @var GroupPrincipalBackend */ private $principalBackend; /** @var CalDavBackend */ private $calDavBackEnd; /** @var CardDavBackend */ private $cardDavBackEnd; /** * BirthdayService constructor. * * @param CalDavBackend $calDavBackEnd * @param CardDavBackend $cardDavBackEnd * @param GroupPrincipalBackend $principalBackend */ public function __construct(CalDavBackend $calDavBackEnd, CardDavBackend $cardDavBackEnd, GroupPrincipalBackend $principalBackend) { $this->calDavBackEnd = $calDavBackEnd; $this->cardDavBackEnd = $cardDavBackEnd; $this->principalBackend = $principalBackend; } /** * @param int $addressBookId * @param string $cardUri * @param string $cardData */ public function onCardChanged($addressBookId, $cardUri, $cardData) { $targetPrincipals = $this->getAllAffectedPrincipals($addressBookId); $book = $this->cardDavBackEnd->getAddressBookById($addressBookId); $targetPrincipals[] = $book['principaluri']; $datesToSync = [ ['postfix' => '', 'field' => 'BDAY', 'symbol' => '*'], ['postfix' => '-death', 'field' => 'DEATHDATE', 'symbol' => "†"], ['postfix' => '-anniversary', 'field' => 'ANNIVERSARY', 'symbol' => "⚭"], ]; foreach ($targetPrincipals as $principalUri) { $calendar = $this->ensureCalendarExists($principalUri); foreach ($datesToSync as $type) { $this->updateCalendar($cardUri, $cardData, $book, $calendar['id'], $type); } } } /** * @param int $addressBookId * @param string $cardUri */ public function onCardDeleted($addressBookId, $cardUri) { $targetPrincipals = $this->getAllAffectedPrincipals($addressBookId); $book = $this->cardDavBackEnd->getAddressBookById($addressBookId); $targetPrincipals[] = $book['principaluri']; foreach ($targetPrincipals as $principalUri) { $calendar = $this->ensureCalendarExists($principalUri); foreach (['', '-death', '-anniversary'] as $tag) { $objectUri = $book['uri'] . '-' . $cardUri . $tag .'.ics'; $this->calDavBackEnd->deleteCalendarObject($calendar['id'], $objectUri); } } } /** * @param string $principal * @return array|null * @throws \Sabre\DAV\Exception\BadRequest */ public function ensureCalendarExists($principal) { $book = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI); if (!is_null($book)) { return $book; } $this->calDavBackEnd->createCalendar($principal, self::BIRTHDAY_CALENDAR_URI, [ '{DAV:}displayname' => 'Contact birthdays', '{http://apple.com/ns/ical/}calendar-color' => '#FFFFCA', 'components' => 'VEVENT', ]); return $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI); } /** * @param string $cardData * @param string $dateField * @param string $summarySymbol * @return null|VCalendar */ public function buildDateFromContact($cardData, $dateField, $summarySymbol) { if (empty($cardData)) { return null; } try { $doc = Reader::read($cardData); // We're always converting to vCard 4.0 so we can rely on the // VCardConverter handling the X-APPLE-OMIT-YEAR property for us. if (!$doc instanceof VCard) { return null; } $doc = $doc->convert(Document::VCARD40); } catch (Exception $e) { return null; } if (!isset($doc->{$dateField})) { return null; } if (!isset($doc->FN)) { return null; } $birthday = $doc->{$dateField}; if (!(string)$birthday) { return null; } // Skip if the BDAY property is not of the right type. if (!$birthday instanceof DateAndOrTime) { return null; } // Skip if we can't parse the BDAY value. try { $dateParts = DateTimeParser::parseVCardDateTime($birthday->getValue()); } catch (InvalidDataException $e) { return null; } $unknownYear = false; if (!$dateParts['year']) { $birthday = '1900-' . $dateParts['month'] . '-' . $dateParts['date']; $unknownYear = true; } try { $date = new \DateTime($birthday); } catch (Exception $e) { return null; } if ($unknownYear) { $summary = $doc->FN->getValue() . ' ' . $summarySymbol; } else { $year = (int)$date->format('Y'); $summary = $doc->FN->getValue() . " ($summarySymbol$year)"; } $vCal = new VCalendar(); $vCal->VERSION = '2.0'; $vEvent = $vCal->createComponent('VEVENT'); $vEvent->add('DTSTART'); $vEvent->DTSTART->setDateTime( $date ); $vEvent->DTSTART['VALUE'] = 'DATE'; $vEvent->add('DTEND'); $date->add(new \DateInterval('P1D')); $vEvent->DTEND->setDateTime( $date ); $vEvent->DTEND['VALUE'] = 'DATE'; $vEvent->{'UID'} = $doc->UID; $vEvent->{'RRULE'} = 'FREQ=YEARLY'; $vEvent->{'SUMMARY'} = $summary; $vEvent->{'TRANSP'} = 'TRANSPARENT'; $alarm = $vCal->createComponent('VALARM'); $alarm->add($vCal->createProperty('TRIGGER', '-PT0M', ['VALUE' => 'DURATION'])); $alarm->add($vCal->createProperty('ACTION', 'DISPLAY')); $alarm->add($vCal->createProperty('DESCRIPTION', $vEvent->{'SUMMARY'})); $vEvent->add($alarm); $vCal->add($vEvent); return $vCal; } /** * @param string $user */ public function syncUser($user) { $principal = 'principals/users/'.$user; $this->ensureCalendarExists($principal); $books = $this->cardDavBackEnd->getAddressBooksForUser($principal); foreach($books as $book) { $cards = $this->cardDavBackEnd->getCards($book['id']); foreach($cards as $card) { $this->onCardChanged($book['id'], $card['uri'], $card['carddata']); } } } /** * @param string $existingCalendarData * @param VCalendar $newCalendarData * @return bool */ public function birthdayEvenChanged($existingCalendarData, $newCalendarData) { try { $existingBirthday = Reader::read($existingCalendarData); } catch (Exception $ex) { return true; } if ($newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() || $newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue() ) { return true; } return false; } /** * @param integer $addressBookId * @return mixed */ protected function getAllAffectedPrincipals($addressBookId) { $targetPrincipals = []; $shares = $this->cardDavBackEnd->getShares($addressBookId); foreach ($shares as $share) { if ($share['{http://owncloud.org/ns}group-share']) { $users = $this->principalBackend->getGroupMemberSet($share['{http://owncloud.org/ns}principal']); foreach ($users as $user) { $targetPrincipals[] = $user['uri']; } } else { $targetPrincipals[] = $share['{http://owncloud.org/ns}principal']; } } return array_values(array_unique($targetPrincipals, SORT_STRING)); } /** * @param string $cardUri * @param string $cardData * @param array $book * @param int $calendarId * @param string $type */ private function updateCalendar($cardUri, $cardData, $book, $calendarId, $type) { $objectUri = $book['uri'] . '-' . $cardUri . $type['postfix'] . '.ics'; $calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['symbol']); $existing = $this->calDavBackEnd->getCalendarObject($calendarId, $objectUri); if (is_null($calendarData)) { if (!is_null($existing)) { $this->calDavBackEnd->deleteCalendarObject($calendarId, $objectUri); } } else { if (is_null($existing)) { $this->calDavBackEnd->createCalendarObject($calendarId, $objectUri, $calendarData->serialize()); } else { if ($this->birthdayEvenChanged($existing['calendardata'], $calendarData)) { $this->calDavBackEnd->updateCalendarObject($calendarId, $objectUri, $calendarData->serialize()); } } } } } lib/CalDAV/Calendar.php 0000604 00000021367 15247100403 0010567 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use OCA\DAV\DAV\Sharing\IShareable; use OCP\IL10N; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; /** * Class Calendar * * @package OCA\DAV\CalDAV * @property BackendInterface|CalDavBackend $caldavBackend */ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n) { parent::__construct($caldavBackend, $calendarInfo); if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Contact birthdays'); } if ($this->getName() === CalDavBackend::PERSONAL_CALENDAR_URI && $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Personal'); } } /** * Updates the list of shares. * * The first array is a list of people that are to be added to the * resource. * * Every element in the add array has the following properties: * * href - A url. Usually a mailto: address * * commonName - Usually a first and last name, or false * * summary - A description of the share, can also be false * * readOnly - A boolean value * * Every element in the remove array is just the address string. * * @param array $add * @param array $remove * @return void * @throws Forbidden */ public function updateShares(array $add, array $remove) { if ($this->isShared()) { throw new Forbidden(); } $this->caldavBackend->updateShares($this, $add, $remove); } /** * Returns the list of people whom this resource is shared with. * * Every element in this array should have the following properties: * * href - Often a mailto: address * * commonName - Optional, for example a first + last name * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. * * readOnly - boolean * * summary - Optional, a description for the share * * @return array */ public function getShares() { if ($this->isShared()) { return []; } return $this->caldavBackend->getShares($this->getResourceId()); } /** * @return int */ public function getResourceId() { return $this->calendarInfo['id']; } /** * @return string */ public function getPrincipalURI() { return $this->calendarInfo['principaluri']; } public function getACL() { $acl = [ [ 'privilege' => '{DAV:}read', 'principal' => $this->getOwner(), 'protected' => true, ]]; if ($this->getName() !== BirthdayService::BIRTHDAY_CALENDAR_URI) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => $this->getOwner(), 'protected' => true, ]; } else { $acl[] = [ 'privilege' => '{DAV:}write-properties', 'principal' => $this->getOwner(), 'protected' => true, ]; } if ($this->getOwner() !== parent::getOwner()) { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => parent::getOwner(), 'protected' => true, ]; if ($this->canWrite()) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => parent::getOwner(), 'protected' => true, ]; } else { $acl[] = [ 'privilege' => '{DAV:}write-properties', 'principal' => parent::getOwner(), 'protected' => true, ]; } } if ($this->isPublic()) { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => 'principals/system/public', 'protected' => true, ]; } $acl = $this->caldavBackend->applyShareAcl($this->getResourceId(), $acl); if (!$this->isShared()) { return $acl; } $allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/public']; return array_filter($acl, function($rule) use ($allowedPrincipals) { return in_array($rule['principal'], $allowedPrincipals); }); } public function getChildACL() { return $this->getACL(); } public function getOwner() { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; } return parent::getOwner(); } public function delete() { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']) && $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']) { $principal = 'principal:' . parent::getOwner(); $shares = $this->caldavBackend->getShares($this->getResourceId()); $shares = array_filter($shares, function($share) use ($principal){ return $share['href'] === $principal; }); if (empty($shares)) { throw new Forbidden(); } $this->caldavBackend->updateShares($this, [], [ 'href' => $principal ]); return; } parent::delete(); } public function propPatch(PropPatch $propPatch) { // parent::propPatch will only update calendars table // if calendar is shared, changes have to be made to the properties table if (!$this->isShared()) { parent::propPatch($propPatch); } } public function getChild($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); if (!$obj) { throw new NotFound('Calendar object not found'); } if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { throw new NotFound('Calendar object not found'); } $obj['acl'] = $this->getChildACL(); return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } public function getChildren() { $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } public function getMultipleChildren(array $paths) { $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } public function childExists($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); if (!$obj) { return false; } if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { return false; } return true; } public function calendarQuery(array $filters) { $uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); if ($this->isShared()) { return array_filter($uris, function ($uri) { return $this->childExists($uri); }); } return $uris; } /** * @param boolean $value * @return string|null */ public function setPublishStatus($value) { $publicUri = $this->caldavBackend->setPublishStatus($value, $this); $this->calendarInfo['publicuri'] = $publicUri; return $publicUri; } /** * @return mixed $value */ public function getPublishStatus() { return $this->caldavBackend->getPublishStatus($this); } private function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; } return true; } private function isPublic() { return isset($this->calendarInfo['{http://owncloud.org/ns}public']); } protected function isShared() { if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return false; } return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']; } public function isSubscription() { return isset($this->calendarInfo['{http://calendarserver.org/ns/}source']); } } lib/CalDAV/CalDavBackend.php 0000604 00000214211 15247100403 0011450 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017 Georg Ehrke * * @author Joas Schilling <coding@schilljs.com> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Citharel <tcit@tcit.fr> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use OCA\DAV\DAV\Sharing\IShareable; use OCP\DB\QueryBuilder\IQueryBuilder; use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\Sharing\Backend; use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; use OCP\Security\ISecureRandom; use Sabre\CalDAV\Backend\AbstractBackend; use Sabre\CalDAV\Backend\SchedulingSupport; use Sabre\CalDAV\Backend\SubscriptionSupport; use Sabre\CalDAV\Backend\SyncSupport; use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; use Sabre\HTTP\URLUtil; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Reader; use Sabre\VObject\Recur\EventIterator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; /** * Class CalDavBackend * * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php * * @package OCA\DAV\CalDAV */ class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport { const PERSONAL_CALENDAR_URI = 'personal'; const PERSONAL_CALENDAR_NAME = 'Personal'; /** * We need to specify a max date, because we need to stop *somewhere* * * On 32 bit system the maximum for a signed integer is 2147483647, so * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results * in 2038-01-19 to avoid problems when the date is converted * to a unix timestamp. */ const MAX_DATE = '2038-01-01'; const ACCESS_PUBLIC = 4; const CLASSIFICATION_PUBLIC = 0; const CLASSIFICATION_PRIVATE = 1; const CLASSIFICATION_CONFIDENTIAL = 2; /** * List of CalDAV properties, and how they map to database field names * Add your own properties by simply adding on to this array. * * Note that only string-based properties are supported here. * * @var array */ public $propertyMap = [ '{DAV:}displayname' => 'displayname', '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description', '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone', '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', ]; /** * List of subscription properties, and how they map to database field names. * * @var array */ public $subscriptionPropertyMap = [ '{DAV:}displayname' => 'displayname', '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate', '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos', '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms', '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments', ]; /** @var array properties to index */ public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION', 'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT', 'ORGANIZER']; /** @var array parameters to index */ public static $indexParameters = [ 'ATTENDEE' => ['CN'], 'ORGANIZER' => ['CN'], ]; /** * @var string[] Map of uid => display name */ protected $userDisplayNames; /** @var IDBConnection */ private $db; /** @var Backend */ private $sharingBackend; /** @var Principal */ private $principalBackend; /** @var IUserManager */ private $userManager; /** @var ISecureRandom */ private $random; /** @var EventDispatcherInterface */ private $dispatcher; /** @var bool */ private $legacyEndpoint; /** @var string */ private $dbObjectPropertiesTable = 'calendarobjects_props'; /** * CalDavBackend constructor. * * @param IDBConnection $db * @param Principal $principalBackend * @param IUserManager $userManager * @param ISecureRandom $random * @param EventDispatcherInterface $dispatcher * @param bool $legacyEndpoint */ public function __construct(IDBConnection $db, Principal $principalBackend, IUserManager $userManager, ISecureRandom $random, EventDispatcherInterface $dispatcher, $legacyEndpoint = false) { $this->db = $db; $this->principalBackend = $principalBackend; $this->userManager = $userManager; $this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar'); $this->random = $random; $this->dispatcher = $dispatcher; $this->legacyEndpoint = $legacyEndpoint; } /** * Return the number of calendars for a principal * * By default this excludes the automatically generated birthday calendar * * @param $principalUri * @param bool $excludeBirthday * @return int */ public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) { $principalUri = $this->convertPrincipal($principalUri, true); $query = $this->db->getQueryBuilder(); $query->select($query->createFunction('COUNT(*)')) ->from('calendars') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))); if ($excludeBirthday) { $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI))); } return (int)$query->execute()->fetchColumn(); } /** * Returns a list of calendars for a principal. * * Every project is an array with the following keys: * * id, a unique id that will be used by other functions to modify the * calendar. This can be the same as the uri or a database key. * * uri, which the basename of the uri with which the calendar is * accessed. * * principaluri. The owner of the calendar. Almost always the same as * principalUri passed to this method. * * Furthermore it can contain webdav properties in clark notation. A very * common one is '{DAV:}displayname'. * * Many clients also require: * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set * For this property, you can just return an instance of * Sabre\CalDAV\Property\SupportedCalendarComponentSet. * * If you return {http://sabredav.org/ns}read-only and set the value to 1, * ACL will automatically be put in read-only mode. * * @param string $principalUri * @return array */ function getCalendarsForUser($principalUri) { $principalUriOriginal = $principalUri; $principalUri = $this->convertPrincipal($principalUri, true); $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->orderBy('calendarorder', 'ASC'); $stmt = $query->execute(); $calendars = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); if (!isset($calendars[$calendar['id']])) { $calendars[$calendar['id']] = $calendar; } } $stmt->closeCursor(); // query for shared calendars $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true); $principals = array_map(function($principal) { return urldecode($principal); }, $principals); $principals[]= $principalUri; $fields = array_values($this->propertyMap); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; $fields[] = 'a.components'; $fields[] = 'a.principaluri'; $fields[] = 'a.transparent'; $fields[] = 's.access'; $query = $this->db->getQueryBuilder(); $result = $query->select($fields) ->from('dav_shares', 's') ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri'))) ->andWhere($query->expr()->eq('s.type', $query->createParameter('type'))) ->setParameter('type', 'calendar') ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY) ->execute(); $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only'; while($row = $result->fetch()) { if ($row['principaluri'] === $principalUri) { continue; } $readOnly = (int) $row['access'] === Backend::ACCESS_READ; if (isset($calendars[$row['id']])) { if ($readOnly) { // New share can not have more permissions then the old one. continue; } if (isset($calendars[$row['id']][$readOnlyPropertyName]) && $calendars[$row['id']][$readOnlyPropertyName] === 0) { // Old share is already read-write, no more permissions can be gained continue; } } list(, $name) = URLUtil::splitPath($row['principaluri']); $uri = $row['uri'] . '_shared_by_' . $name; $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')'; $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $uri, 'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), $readOnlyPropertyName => $readOnly, ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); $calendars[$calendar['id']] = $calendar; } $result->closeCursor(); return array_values($calendars); } public function getUsersOwnCalendars($principalUri) { $principalUri = $this->convertPrincipal($principalUri, true); $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->orderBy('calendarorder', 'ASC'); $stmt = $query->execute(); $calendars = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); if (!isset($calendars[$calendar['id']])) { $calendars[$calendar['id']] = $calendar; } } $stmt->closeCursor(); return array_values($calendars); } private function getUserDisplayName($uid) { if (!isset($this->userDisplayNames[$uid])) { $user = $this->userManager->get($uid); if ($user instanceof IUser) { $this->userDisplayNames[$uid] = $user->getDisplayName(); } else { $this->userDisplayNames[$uid] = $uid; } } return $this->userDisplayNames[$uid]; } /** * @return array */ public function getPublicCalendars() { $fields = array_values($this->propertyMap); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; $fields[] = 'a.components'; $fields[] = 'a.principaluri'; $fields[] = 'a.transparent'; $fields[] = 's.access'; $fields[] = 's.publicuri'; $calendars = []; $query = $this->db->getQueryBuilder(); $result = $query->select($fields) ->from('dav_shares', 's') ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar'))) ->execute(); while($row = $result->fetch()) { list(, $name) = URLUtil::splitPath($row['principaluri']); $row['displayname'] = $row['displayname'] . "($name)"; $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['publicuri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ, '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); if (!isset($calendars[$calendar['id']])) { $calendars[$calendar['id']] = $calendar; } } $result->closeCursor(); return array_values($calendars); } /** * @param string $uri * @return array * @throws NotFound */ public function getPublicCalendar($uri) { $fields = array_values($this->propertyMap); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; $fields[] = 'a.components'; $fields[] = 'a.principaluri'; $fields[] = 'a.transparent'; $fields[] = 's.access'; $fields[] = 's.publicuri'; $query = $this->db->getQueryBuilder(); $result = $query->select($fields) ->from('dav_shares', 's') ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar'))) ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri))) ->execute(); $row = $result->fetch(\PDO::FETCH_ASSOC); $result->closeCursor(); if ($row === false) { throw new NotFound('Node with name \'' . $uri . '\' could not be found'); } list(, $name) = URLUtil::splitPath($row['principaluri']); $row['displayname'] = $row['displayname'] . ' ' . "($name)"; $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['publicuri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ, '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); return $calendar; } /** * @param string $principal * @param string $uri * @return array|null */ public function getCalendarByUri($principal, $uri) { $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal))) ->setMaxResults(1); $stmt = $query->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); $stmt->closeCursor(); if ($row === false) { return null; } $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); return $calendar; } public function getCalendarById($calendarId) { $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId))) ->setMaxResults(1); $stmt = $query->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); $stmt->closeCursor(); if ($row === false) { return null; } $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); return $calendar; } /** * Creates a new calendar for a principal. * * If the creation was a success, an id must be returned that can be used to reference * this calendar in other methods, such as updateCalendar. * * @param string $principalUri * @param string $calendarUri * @param array $properties * @return int */ function createCalendar($principalUri, $calendarUri, array $properties) { $values = [ 'principaluri' => $this->convertPrincipal($principalUri, true), 'uri' => $calendarUri, 'synctoken' => 1, 'transparent' => 0, 'components' => 'VEVENT,VTODO', 'displayname' => $calendarUri ]; // Default value $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; if (isset($properties[$sccs])) { if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) { throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); } $values['components'] = implode(',',$properties[$sccs]->getValue()); } $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp'; if (isset($properties[$transp])) { $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent'); } foreach($this->propertyMap as $xmlName=>$dbName) { if (isset($properties[$xmlName])) { $values[$dbName] = $properties[$xmlName]; } } $query = $this->db->getQueryBuilder(); $query->insert('calendars'); foreach($values as $column => $value) { $query->setValue($column, $query->createNamedParameter($value)); } $query->execute(); $calendarId = $query->getLastInsertId(); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::createCalendar', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), ])); return $calendarId; } /** * Updates properties for a calendar. * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param PropPatch $propPatch * @return void */ function updateCalendar($calendarId, PropPatch $propPatch) { $supportedProperties = array_keys($this->propertyMap); $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp'; $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) { $newValues = []; foreach ($mutations as $propertyName => $propertyValue) { switch ($propertyName) { case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' : $fieldName = 'transparent'; $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent'); break; default : $fieldName = $this->propertyMap[$propertyName]; $newValues[$fieldName] = $propertyValue; break; } } $query = $this->db->getQueryBuilder(); $query->update('calendars'); foreach ($newValues as $fieldName => $value) { $query->set($fieldName, $query->createNamedParameter($value)); } $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId))); $query->execute(); $this->addChange($calendarId, "", 2); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'propertyMutations' => $mutations, ])); return true; }); } /** * Delete a calendar and all it's objects * * @param mixed $calendarId * @return void */ function deleteCalendar($calendarId) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), ])); $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?'); $stmt->execute([$calendarId]); $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?'); $stmt->execute([$calendarId]); $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?'); $stmt->execute([$calendarId]); $this->sharingBackend->deleteAllShares($calendarId); $query = $this->db->getQueryBuilder(); $query->delete($this->dbObjectPropertiesTable) ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->execute(); } /** * Delete all of an user's shares * * @param string $principaluri * @return void */ function deleteAllSharesByUser($principaluri) { $this->sharingBackend->deleteAllSharesByUser($principaluri); } /** * Returns all calendar objects within a calendar. * * Every item contains an array with the following keys: * * calendardata - The iCalendar-compatible calendar data * * uri - a unique key which will be used to construct the uri. This can * be any arbitrary string, but making sure it ends with '.ics' is a * good idea. This is only the basename, or filename, not the full * path. * * lastmodified - a timestamp of the last modification time * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: * '"abcdef"') * * size - The size of the calendar objects, in bytes. * * component - optional, a string containing the type of object, such * as 'vevent' or 'vtodo'. If specified, this will be used to populate * the Content-Type header. * * Note that the etag is optional, but it's highly encouraged to return for * speed reasons. * * The calendardata is also optional. If it's not returned * 'getCalendarObject' will be called later, which *is* expected to return * calendardata. * * If neither etag or size are specified, the calendardata will be * used/fetched to determine these numbers. If both are specified the * amount of times this is needed is reduced by a great degree. * * @param mixed $calendarId * @return array */ function getCalendarObjects($calendarId) { $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $stmt = $query->execute(); $result = []; foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $result[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'component' => strtolower($row['componenttype']), 'classification'=> (int)$row['classification'] ]; } return $result; } /** * Returns information from a single calendar object, based on it's object * uri. * * The object uri is only the basename, or filename and not a full path. * * The returned array must have the same keys as getCalendarObjects. The * 'calendardata' object is required here though, while it's not required * for getCalendarObjects. * * This method must return null if the object did not exist. * * @param mixed $calendarId * @param string $objectUri * @return array|null */ function getCalendarObject($calendarId, $objectUri) { $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))); $stmt = $query->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); if(!$row) return null; return [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), 'classification'=> (int)$row['classification'] ]; } /** * Returns a list of calendar objects. * * This method should work identical to getCalendarObject, but instead * return all the calendar objects in the list as an array. * * If the backend supports this, it may allow for some speed-ups. * * @param mixed $calendarId * @param string[] $uris * @return array */ function getMultipleCalendarObjects($calendarId, array $uris) { if (empty($uris)) { return []; } $chunks = array_chunk($uris, 100); $objects = []; $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->in('uri', $query->createParameter('uri'))); foreach ($chunks as $uris) { $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY); $result = $query->execute(); while ($row = $result->fetch()) { $objects[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), 'classification' => (int)$row['classification'] ]; } $result->closeCursor(); } return $objects; } /** * Creates a new calendar object. * * The object uri is only the basename, or filename and not a full path. * * It is possible return an etag from this function, which will be used in * the response to this PUT request. Note that the ETag must be surrounded * by double-quotes. * * However, you should only really return this ETag if you don't mangle the * calendar-data. If the result of a subsequent GET to this object is not * the exact same as this request body, you should omit the ETag. * * @param mixed $calendarId * @param string $objectUri * @param string $calendarData * @return string */ function createCalendarObject($calendarId, $objectUri, $calendarData) { $extraData = $this->getDenormalizedData($calendarData); $query = $this->db->getQueryBuilder(); $query->insert('calendarobjects') ->values([ 'calendarid' => $query->createNamedParameter($calendarId), 'uri' => $query->createNamedParameter($objectUri), 'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB), 'lastmodified' => $query->createNamedParameter(time()), 'etag' => $query->createNamedParameter($extraData['etag']), 'size' => $query->createNamedParameter($extraData['size']), 'componenttype' => $query->createNamedParameter($extraData['componentType']), 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']), 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']), 'classification' => $query->createNamedParameter($extraData['classification']), 'uid' => $query->createNamedParameter($extraData['uid']), ]) ->execute(); $this->updateProperties($calendarId, $objectUri, $calendarData); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'objectData' => $this->getCalendarObject($calendarId, $objectUri), ] )); $this->addChange($calendarId, $objectUri, 1); return '"' . $extraData['etag'] . '"'; } /** * Updates an existing calendarobject, based on it's uri. * * The object uri is only the basename, or filename and not a full path. * * It is possible return an etag from this function, which will be used in * the response to this PUT request. Note that the ETag must be surrounded * by double-quotes. * * However, you should only really return this ETag if you don't mangle the * calendar-data. If the result of a subsequent GET to this object is not * the exact same as this request body, you should omit the ETag. * * @param mixed $calendarId * @param string $objectUri * @param string $calendarData * @return string */ function updateCalendarObject($calendarId, $objectUri, $calendarData) { $extraData = $this->getDenormalizedData($calendarData); $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB)) ->set('lastmodified', $query->createNamedParameter(time())) ->set('etag', $query->createNamedParameter($extraData['etag'])) ->set('size', $query->createNamedParameter($extraData['size'])) ->set('componenttype', $query->createNamedParameter($extraData['componentType'])) ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence'])) ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence'])) ->set('classification', $query->createNamedParameter($extraData['classification'])) ->set('uid', $query->createNamedParameter($extraData['uid'])) ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); $this->updateProperties($calendarId, $objectUri, $calendarData); $data = $this->getCalendarObject($calendarId, $objectUri); if (is_array($data)) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'objectData' => $data, ] )); } $this->addChange($calendarId, $objectUri, 2); return '"' . $extraData['etag'] . '"'; } /** * @param int $calendarObjectId * @param int $classification */ public function setClassification($calendarObjectId, $classification) { if (!in_array($classification, [ self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL ])) { throw new \InvalidArgumentException(); } $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('classification', $query->createNamedParameter($classification)) ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId))) ->execute(); } /** * Deletes an existing calendar object. * * The object uri is only the basename, or filename and not a full path. * * @param mixed $calendarId * @param string $objectUri * @return void */ function deleteCalendarObject($calendarId, $objectUri) { $data = $this->getCalendarObject($calendarId, $objectUri); if (is_array($data)) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'objectData' => $data, ] )); } $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?'); $stmt->execute([$calendarId, $objectUri]); $this->purgeProperties($calendarId, $data['id']); $this->addChange($calendarId, $objectUri, 3); } /** * Performs a calendar-query on the contents of this calendar. * * The calendar-query is defined in RFC4791 : CalDAV. Using the * calendar-query it is possible for a client to request a specific set of * object, based on contents of iCalendar properties, date-ranges and * iCalendar component types (VTODO, VEVENT). * * This method should just return a list of (relative) urls that match this * query. * * The list of filters are specified as an array. The exact array is * documented by Sabre\CalDAV\CalendarQueryParser. * * Note that it is extremely likely that getCalendarObject for every path * returned from this method will be called almost immediately after. You * may want to anticipate this to speed up these requests. * * This method provides a default implementation, which parses *all* the * iCalendar objects in the specified calendar. * * This default may well be good enough for personal use, and calendars * that aren't very large. But if you anticipate high usage, big calendars * or high loads, you are strongly advised to optimize certain paths. * * The best way to do so is override this method and to optimize * specifically for 'common filters'. * * Requests that are extremely common are: * * requests for just VEVENTS * * requests for just VTODO * * requests with a time-range-filter on either VEVENT or VTODO. * * ..and combinations of these requests. It may not be worth it to try to * handle every possible situation and just rely on the (relatively * easy to use) CalendarQueryValidator to handle the rest. * * Note that especially time-range-filters may be difficult to parse. A * time-range filter specified on a VEVENT must for instance also handle * recurrence rules correctly. * A good example of how to interprete all these filters can also simply * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct * as possible, so it gives you a good idea on what type of stuff you need * to think of. * * @param mixed $calendarId * @param array $filters * @return array */ function calendarQuery($calendarId, array $filters) { $componentType = null; $requirePostFilter = true; $timeRange = null; // if no filters were specified, we don't need to filter after a query if (!$filters['prop-filters'] && !$filters['comp-filters']) { $requirePostFilter = false; } // Figuring out if there's a component filter if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) { $componentType = $filters['comp-filters'][0]['name']; // Checking if we need post-filters if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { $requirePostFilter = false; } // There was a time-range filter if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) { $timeRange = $filters['comp-filters'][0]['time-range']; // If start time OR the end time is not specified, we can do a // 100% accurate mysql query. if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) { $requirePostFilter = false; } } } $columns = ['uri']; if ($requirePostFilter) { $columns = ['uri', 'calendardata']; } $query = $this->db->getQueryBuilder(); $query->select($columns) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); if ($componentType) { $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType))); } if ($timeRange && $timeRange['start']) { $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp()))); } if ($timeRange && $timeRange['end']) { $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp()))); } $stmt = $query->execute(); $result = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { if ($requirePostFilter) { if (!$this->validateFilterForObject($row, $filters)) { continue; } } $result[] = $row['uri']; } return $result; } /** * custom Nextcloud search extension for CalDAV * * @param string $principalUri * @param array $filters * @param integer|null $limit * @param integer|null $offset * @return array */ public function calendarSearch($principalUri, array $filters, $limit=null, $offset=null) { $calendars = $this->getCalendarsForUser($principalUri); $ownCalendars = []; $sharedCalendars = []; $uriMapper = []; foreach($calendars as $calendar) { if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) { $ownCalendars[] = $calendar['id']; } else { $sharedCalendars[] = $calendar['id']; } $uriMapper[$calendar['id']] = $calendar['uri']; } if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) { return []; } $query = $this->db->getQueryBuilder(); // Calendar id expressions $calendarExpressions = []; foreach($ownCalendars as $id) { $calendarExpressions[] = $query->expr() ->eq('c.calendarid', $query->createNamedParameter($id)); } foreach($sharedCalendars as $id) { $calendarExpressions[] = $query->expr()->andX( $query->expr()->eq('c.calendarid', $query->createNamedParameter($id)), $query->expr()->eq('c.classification', $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)) ); } if (count($calendarExpressions) === 1) { $calExpr = $calendarExpressions[0]; } else { $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions); } // Component expressions $compExpressions = []; foreach($filters['comps'] as $comp) { $compExpressions[] = $query->expr() ->eq('c.componenttype', $query->createNamedParameter($comp)); } if (count($compExpressions) === 1) { $compExpr = $compExpressions[0]; } else { $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions); } if (!isset($filters['props'])) { $filters['props'] = []; } if (!isset($filters['params'])) { $filters['params'] = []; } $propParamExpressions = []; foreach($filters['props'] as $prop) { $propParamExpressions[] = $query->expr()->andX( $query->expr()->eq('i.name', $query->createNamedParameter($prop)), $query->expr()->isNull('i.parameter') ); } foreach($filters['params'] as $param) { $propParamExpressions[] = $query->expr()->andX( $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])), $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter'])) ); } if (count($propParamExpressions) === 1) { $propParamExpr = $propParamExpressions[0]; } else { $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions); } $query->select(['c.calendarid', 'c.uri']) ->from($this->dbObjectPropertiesTable, 'i') ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id')) ->where($calExpr) ->andWhere($compExpr) ->andWhere($propParamExpr) ->andWhere($query->expr()->iLike('i.value', $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%'))); if ($offset) { $query->setFirstResult($offset); } if ($limit) { $query->setMaxResults($limit); } $stmt = $query->execute(); $result = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $path = $uriMapper[$row['calendarid']] . '/' . $row['uri']; if (!in_array($path, $result)) { $result[] = $path; } } return $result; } /** * Searches through all of a users calendars and calendar objects to find * an object with a specific UID. * * This method should return the path to this object, relative to the * calendar home, so this path usually only contains two parts: * * calendarpath/objectpath.ics * * If the uid is not found, return null. * * This method should only consider * objects that the principal owns, so * any calendars owned by other principals that also appear in this * collection should be ignored. * * @param string $principalUri * @param string $uid * @return string|null */ function getCalendarObjectByUID($principalUri, $uid) { $query = $this->db->getQueryBuilder(); $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi') ->from('calendarobjects', 'co') ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id')) ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid))); $stmt = $query->execute(); if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { return $row['calendaruri'] . '/' . $row['objecturi']; } return null; } /** * The getChanges method returns all the changes that have happened, since * the specified syncToken in the specified calendar. * * This function should return an array, such as the following: * * [ * 'syncToken' => 'The current synctoken', * 'added' => [ * 'new.txt', * ], * 'modified' => [ * 'modified.txt', * ], * 'deleted' => [ * 'foo.php.bak', * 'old.txt' * ] * ); * * The returned syncToken property should reflect the *current* syncToken * of the calendar, as reported in the {http://sabredav.org/ns}sync-token * property This is * needed here too, to ensure the operation is atomic. * * If the $syncToken argument is specified as null, this is an initial * sync, and all members should be reported. * * The modified property is an array of nodenames that have changed since * the last token. * * The deleted property is an array with nodenames, that have been deleted * from collection. * * The $syncLevel argument is basically the 'depth' of the report. If it's * 1, you only have to report changes that happened only directly in * immediate descendants. If it's 2, it should also include changes from * the nodes below the child collections. (grandchildren) * * The $limit argument allows a client to specify how many results should * be returned at most. If the limit is not specified, it should be treated * as infinite. * * If the limit (infinite or not) is higher than you're willing to return, * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. * * If the syncToken is expired (due to data cleanup) or unknown, you must * return null. * * The limit is 'suggestive'. You are free to ignore it. * * @param string $calendarId * @param string $syncToken * @param int $syncLevel * @param int $limit * @return array */ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) { // Current synctoken $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?'); $stmt->execute([ $calendarId ]); $currentToken = $stmt->fetchColumn(0); if (is_null($currentToken)) { return null; } $result = [ 'syncToken' => $currentToken, 'added' => [], 'modified' => [], 'deleted' => [], ]; if ($syncToken) { $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`"; if ($limit>0) { $query.= " LIMIT " . (int)$limit; } // Fetching all changes $stmt = $this->db->prepare($query); $stmt->execute([$syncToken, $currentToken, $calendarId]); $changes = []; // This loop ensures that any duplicates are overwritten, only the // last change on a node is relevant. while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; } foreach($changes as $uri => $operation) { switch($operation) { case 1 : $result['added'][] = $uri; break; case 2 : $result['modified'][] = $uri; break; case 3 : $result['deleted'][] = $uri; break; } } } else { // No synctoken supplied, this is the initial sync. $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?"; $stmt = $this->db->prepare($query); $stmt->execute([$calendarId]); $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); } return $result; } /** * Returns a list of subscriptions for a principal. * * Every subscription is an array with the following keys: * * id, a unique id that will be used by other functions to modify the * subscription. This can be the same as the uri or a database key. * * uri. This is just the 'base uri' or 'filename' of the subscription. * * principaluri. The owner of the subscription. Almost always the same as * principalUri passed to this method. * * Furthermore, all the subscription info must be returned too: * * 1. {DAV:}displayname * 2. {http://apple.com/ns/ical/}refreshrate * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos * should not be stripped). * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms * should not be stripped). * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if * attachments should not be stripped). * 6. {http://calendarserver.org/ns/}source (Must be a * Sabre\DAV\Property\Href). * 7. {http://apple.com/ns/ical/}calendar-color * 8. {http://apple.com/ns/ical/}calendar-order * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set * (should just be an instance of * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of * default components). * * @param string $principalUri * @return array */ function getSubscriptionsForUser($principalUri) { $fields = array_values($this->subscriptionPropertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'source'; $fields[] = 'principaluri'; $fields[] = 'lastmodified'; $query = $this->db->getQueryBuilder(); $query->select($fields) ->from('calendarsubscriptions') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->orderBy('calendarorder', 'asc'); $stmt =$query->execute(); $subscriptions = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $subscription = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $row['principaluri'], 'source' => $row['source'], 'lastmodified' => $row['lastmodified'], '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']), ]; foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) { if (!is_null($row[$dbName])) { $subscription[$xmlName] = $row[$dbName]; } } $subscriptions[] = $subscription; } return $subscriptions; } /** * Creates a new subscription for a principal. * * If the creation was a success, an id must be returned that can be used to reference * this subscription in other methods, such as updateSubscription. * * @param string $principalUri * @param string $uri * @param array $properties * @return mixed */ function createSubscription($principalUri, $uri, array $properties) { if (!isset($properties['{http://calendarserver.org/ns/}source'])) { throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions'); } $values = [ 'principaluri' => $principalUri, 'uri' => $uri, 'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(), 'lastmodified' => time(), ]; $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments']; foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) { if (array_key_exists($xmlName, $properties)) { $values[$dbName] = $properties[$xmlName]; if (in_array($dbName, $propertiesBoolean)) { $values[$dbName] = true; } } } $valuesToInsert = array(); $query = $this->db->getQueryBuilder(); foreach (array_keys($values) as $name) { $valuesToInsert[$name] = $query->createNamedParameter($values[$name]); } $query->insert('calendarsubscriptions') ->values($valuesToInsert) ->execute(); return $this->db->lastInsertId('*PREFIX*calendarsubscriptions'); } /** * Updates a subscription * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param mixed $subscriptionId * @param PropPatch $propPatch * @return void */ function updateSubscription($subscriptionId, PropPatch $propPatch) { $supportedProperties = array_keys($this->subscriptionPropertyMap); $supportedProperties[] = '{http://calendarserver.org/ns/}source'; $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) { $newValues = []; foreach($mutations as $propertyName=>$propertyValue) { if ($propertyName === '{http://calendarserver.org/ns/}source') { $newValues['source'] = $propertyValue->getHref(); } else { $fieldName = $this->subscriptionPropertyMap[$propertyName]; $newValues[$fieldName] = $propertyValue; } } $query = $this->db->getQueryBuilder(); $query->update('calendarsubscriptions') ->set('lastmodified', $query->createNamedParameter(time())); foreach($newValues as $fieldName=>$value) { $query->set($fieldName, $query->createNamedParameter($value)); } $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId))) ->execute(); return true; }); } /** * Deletes a subscription. * * @param mixed $subscriptionId * @return void */ function deleteSubscription($subscriptionId) { $query = $this->db->getQueryBuilder(); $query->delete('calendarsubscriptions') ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId))) ->execute(); } /** * Returns a single scheduling object for the inbox collection. * * The returned array should contain the following elements: * * uri - A unique basename for the object. This will be used to * construct a full uri. * * calendardata - The iCalendar object * * lastmodified - The last modification date. Can be an int for a unix * timestamp, or a PHP DateTime object. * * etag - A unique token that must change if the object changed. * * size - The size of the object, in bytes. * * @param string $principalUri * @param string $objectUri * @return array */ function getSchedulingObject($principalUri, $objectUri) { $query = $this->db->getQueryBuilder(); $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size']) ->from('schedulingobjects') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); if(!$row) { return null; } return [ 'uri' => $row['uri'], 'calendardata' => $row['calendardata'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'size' => (int)$row['size'], ]; } /** * Returns all scheduling objects for the inbox collection. * * These objects should be returned as an array. Every item in the array * should follow the same structure as returned from getSchedulingObject. * * The main difference is that 'calendardata' is optional. * * @param string $principalUri * @return array */ function getSchedulingObjects($principalUri) { $query = $this->db->getQueryBuilder(); $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size']) ->from('schedulingobjects') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->execute(); $result = []; foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $result[] = [ 'calendardata' => $row['calendardata'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'size' => (int)$row['size'], ]; } return $result; } /** * Deletes a scheduling object from the inbox collection. * * @param string $principalUri * @param string $objectUri * @return void */ function deleteSchedulingObject($principalUri, $objectUri) { $query = $this->db->getQueryBuilder(); $query->delete('schedulingobjects') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); } /** * Creates a new scheduling object. This should land in a users' inbox. * * @param string $principalUri * @param string $objectUri * @param string $objectData * @return void */ function createSchedulingObject($principalUri, $objectUri, $objectData) { $query = $this->db->getQueryBuilder(); $query->insert('schedulingobjects') ->values([ 'principaluri' => $query->createNamedParameter($principalUri), 'calendardata' => $query->createNamedParameter($objectData), 'uri' => $query->createNamedParameter($objectUri), 'lastmodified' => $query->createNamedParameter(time()), 'etag' => $query->createNamedParameter(md5($objectData)), 'size' => $query->createNamedParameter(strlen($objectData)) ]) ->execute(); } /** * Adds a change record to the calendarchanges table. * * @param mixed $calendarId * @param string $objectUri * @param int $operation 1 = add, 2 = modify, 3 = delete. * @return void */ protected function addChange($calendarId, $objectUri, $operation) { $stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?'); $stmt->execute([ $objectUri, $calendarId, $operation, $calendarId ]); $stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?'); $stmt->execute([ $calendarId ]); } /** * Parses some information from calendar objects, used for optimized * calendar-queries. * * Returns an array with the following keys: * * etag - An md5 checksum of the object without the quotes. * * size - Size of the object in bytes * * componentType - VEVENT, VTODO or VJOURNAL * * firstOccurence * * lastOccurence * * uid - value of the UID property * * @param string $calendarData * @return array */ public function getDenormalizedData($calendarData) { $vObject = Reader::read($calendarData); $componentType = null; $component = null; $firstOccurrence = null; $lastOccurrence = null; $uid = null; $classification = self::CLASSIFICATION_PUBLIC; foreach($vObject->getComponents() as $component) { if ($component->name!=='VTIMEZONE') { $componentType = $component->name; $uid = (string)$component->UID; break; } } if (!$componentType) { throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); } if ($componentType === 'VEVENT' && $component->DTSTART) { $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); $lastOccurrence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); $lastOccurrence = $endDate->getTimeStamp(); } else { $lastOccurrence = $firstOccurrence; } } else { $it = new EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { $lastOccurrence = $maxDate->getTimestamp(); } else { $end = $it->getDtEnd(); while($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurrence = $end->getTimestamp(); } } } if ($component->CLASS) { $classification = CalDavBackend::CLASSIFICATION_PRIVATE; switch ($component->CLASS->getValue()) { case 'PUBLIC': $classification = CalDavBackend::CLASSIFICATION_PUBLIC; break; case 'CONFIDENTIAL': $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL; break; } } return [ 'etag' => md5($calendarData), 'size' => strlen($calendarData), 'componentType' => $componentType, 'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence), 'lastOccurence' => $lastOccurrence, 'uid' => $uid, 'classification' => $classification ]; } private function readBlob($cardData) { if (is_resource($cardData)) { return stream_get_contents($cardData); } return $cardData; } /** * @param IShareable $shareable * @param array $add * @param array $remove */ public function updateShares($shareable, $add, $remove) { $calendarId = $shareable->getResourceId(); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::updateShares', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'add' => $add, 'remove' => $remove, ])); $this->sharingBackend->updateShares($shareable, $add, $remove); } /** * @param int $resourceId * @return array */ public function getShares($resourceId) { return $this->sharingBackend->getShares($resourceId); } /** * @param boolean $value * @param \OCA\DAV\CalDAV\Calendar $calendar * @return string|null */ public function setPublishStatus($value, $calendar) { $query = $this->db->getQueryBuilder(); if ($value) { $publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS); $query->insert('dav_shares') ->values([ 'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()), 'type' => $query->createNamedParameter('calendar'), 'access' => $query->createNamedParameter(self::ACCESS_PUBLIC), 'resourceid' => $query->createNamedParameter($calendar->getResourceId()), 'publicuri' => $query->createNamedParameter($publicUri) ]); $query->execute(); return $publicUri; } $query->delete('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))); $query->execute(); return null; } /** * @param \OCA\DAV\CalDAV\Calendar $calendar * @return mixed */ public function getPublishStatus($calendar) { $query = $this->db->getQueryBuilder(); $result = $query->select('publicuri') ->from('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->execute(); $row = $result->fetch(); $result->closeCursor(); return $row ? reset($row) : false; } /** * @param int $resourceId * @param array $acl * @return array */ public function applyShareAcl($resourceId, $acl) { return $this->sharingBackend->applyShareAcl($resourceId, $acl); } /** * update properties table * * @param int $calendarId * @param string $objectUri * @param string $calendarData */ public function updateProperties($calendarId, $objectUri, $calendarData) { $objectId = $this->getCalendarObjectId($calendarId, $objectUri); try { $vCalendar = $this->readCalendarData($calendarData); } catch (\Exception $ex) { return; } $this->purgeProperties($calendarId, $objectId); $query = $this->db->getQueryBuilder(); $query->insert($this->dbObjectPropertiesTable) ->values( [ 'calendarid' => $query->createNamedParameter($calendarId), 'objectid' => $query->createNamedParameter($objectId), 'name' => $query->createParameter('name'), 'parameter' => $query->createParameter('parameter'), 'value' => $query->createParameter('value'), ] ); $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO']; foreach ($vCalendar->getComponents() as $component) { if (!in_array($component->name, $indexComponents)) { continue; } foreach ($component->children() as $property) { if (in_array($property->name, self::$indexProperties)) { $value = $property->getValue(); // is this a shitty db? if (!$this->db->supports4ByteText()) { $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); } $value = substr($value, 0, 254); $query->setParameter('name', $property->name); $query->setParameter('parameter', null); $query->setParameter('value', $value); $query->execute(); } if (in_array($property->name, array_keys(self::$indexParameters))) { $parameters = $property->parameters(); $indexedParametersForProperty = self::$indexParameters[$property->name]; foreach ($parameters as $key => $value) { if (in_array($key, $indexedParametersForProperty)) { // is this a shitty db? if ($this->db->supports4ByteText()) { $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); } $value = substr($value, 0, 254); $query->setParameter('name', $property->name); $query->setParameter('parameter', substr($key, 0, 254)); $query->setParameter('value', substr($value, 0, 254)); $query->execute(); } } } } } } /** * read VCalendar data into a VCalendar object * * @param string $objectData * @return VCalendar */ protected function readCalendarData($objectData) { return Reader::read($objectData); } /** * delete all properties from a given calendar object * * @param int $calendarId * @param int $objectId */ protected function purgeProperties($calendarId, $objectId) { $query = $this->db->getQueryBuilder(); $query->delete($this->dbObjectPropertiesTable) ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId))) ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $query->execute(); } /** * get ID from a given calendar object * * @param int $calendarId * @param string $uri * @return int */ protected function getCalendarObjectId($calendarId, $uri) { $query = $this->db->getQueryBuilder(); $query->select('id')->from('calendarobjects') ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $result = $query->execute(); $objectIds = $result->fetch(); $result->closeCursor(); if (!isset($objectIds['id'])) { throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri); } return (int)$objectIds['id']; } private function convertPrincipal($principalUri, $toV2) { if ($this->principalBackend->getPrincipalPrefix() === 'principals') { list(, $name) = URLUtil::splitPath($principalUri); if ($toV2 === true) { return "principals/users/$name"; } return "principals/$name"; } return $principalUri; } private function addOwnerPrincipal(&$calendarInfo) { $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal'; $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname'; if (isset($calendarInfo[$ownerPrincipalKey])) { $uri = $calendarInfo[$ownerPrincipalKey]; } else { $uri = $calendarInfo['principaluri']; } $principalInformation = $this->principalBackend->getPrincipalByPath($uri); if (isset($principalInformation['{DAV:}displayname'])) { $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname']; } } } lib/CalDAV/Publishing/Xml/Publisher.php 0000604 00000004176 15247100403 0013656 0 ustar 00 <?php /** * @author Thomas Citharel <tcit@tcit.fr> * * @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Publishing\Xml; use Sabre\Xml\Writer; use Sabre\Xml\XmlSerializable; class Publisher implements XmlSerializable { /** * @var string $publishUrl */ protected $publishUrl; /** * @var boolean $isPublished */ protected $isPublished; /** * @param string $publishUrl * @param boolean $isPublished */ function __construct($publishUrl, $isPublished) { $this->publishUrl = $publishUrl; $this->isPublished = $isPublished; } /** * @return string */ function getValue() { return $this->publishUrl; } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { if (!$this->isPublished) { // for pre-publish-url $writer->write($this->publishUrl); } else { // for publish-url $writer->writeElement('{DAV:}href', $this->publishUrl); } } } lib/CalDAV/Publishing/PublishPlugin.php 0000604 00000014211 15247100403 0013735 0 ustar 00 <?php /** * @author Thomas Citharel <tcit@tcit.fr> * * @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Publishing; use Sabre\DAV\PropFind; use Sabre\DAV\INode; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Exception\NotFound; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\CalDAV\Xml\Property\AllowedSharingModes; use OCA\DAV\CalDAV\Publishing\Xml\Publisher; use OCA\DAV\CalDAV\Calendar; use OCP\IURLGenerator; use OCP\IConfig; class PublishPlugin extends ServerPlugin { const NS_CALENDARSERVER = 'http://calendarserver.org/ns/'; /** * Reference to SabreDAV server object. * * @var \Sabre\DAV\Server */ protected $server; /** * Config instance to get instance secret. * * @var IConfig */ protected $config; /** * URL Generator for absolute URLs. * * @var IURLGenerator */ protected $urlGenerator; /** * PublishPlugin constructor. * * @param IConfig $config * @param IURLGenerator $urlGenerator */ public function __construct(IConfig $config, IURLGenerator $urlGenerator) { $this->config = $config; $this->urlGenerator = $urlGenerator; } /** * This method should return a list of server-features. * * This is for example 'versioning' and is added to the DAV: header * in an OPTIONS response. * * @return string[] */ public function getFeatures() { // May have to be changed to be detected return ['oc-calendar-publishing', 'calendarserver-sharing']; } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using Sabre\DAV\Server::getPlugin * * @return string */ public function getPluginName() { return 'oc-calendar-publishing'; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server */ public function initialize(Server $server) { $this->server = $server; $this->server->on('method:POST', [$this, 'httpPost']); $this->server->on('propFind', [$this, 'propFind']); } public function propFind(PropFind $propFind, INode $node) { if ($node instanceof Calendar) { $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) { if ($node->getPublishStatus()) { // We return the publish-url only if the calendar is published. $token = $node->getPublishStatus(); $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token; return new Publisher($publishUrl, true); } }); $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function() use ($node) { return new AllowedSharingModes(!$node->isSubscription(), !$node->isSubscription()); }); } } /** * We intercept this to handle POST requests on calendars. * * @param RequestInterface $request * @param ResponseInterface $response * * @return void|bool */ public function httpPost(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); // Only handling xml $contentType = $request->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) { return; } // Making sure the node exists try { $node = $this->server->tree->getNodeForPath($path); } catch (NotFound $e) { return; } $requestBody = $request->getBodyAsString(); // If this request handler could not deal with this POST request, it // will return 'null' and other plugins get a chance to handle the // request. // // However, we already requested the full body. This is a problem, // because a body can only be read once. This is why we preemptively // re-populated the request body with the existing data. $request->setBody($requestBody); $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); switch ($documentType) { case '{'.self::NS_CALENDARSERVER.'}publish-calendar' : // We can only deal with IShareableCalendar objects if (!$node instanceof Calendar) { return; } $this->server->transactionType = 'post-publish-calendar'; // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($path, '{DAV:}write'); } $node->setPublishStatus(true); // iCloud sends back the 202, so we will too. $response->setStatus(202); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' : // We can only deal with IShareableCalendar objects if (!$node instanceof Calendar) { return; } $this->server->transactionType = 'post-unpublish-calendar'; // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($path, '{DAV:}write'); } $node->setPublishStatus(false); $response->setStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; } } } lib/CalDAV/PublicCalendarObject.php 0000604 00000001666 15247100403 0013055 0 ustar 00 <?php /** * @copyright Copyright (c) 2017, Georg Ehrke * * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; class PublicCalendarObject extends CalendarObject { /** * public calendars are always shared * @return bool */ protected function isShared() { return true; } } lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php 0000604 00000013255 15247100403 0016503 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Request; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; /** * CalendarSearchReport request parser. * * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-query * REPORT, as defined in: * * https:// link to standard */ class CalendarSearchReport implements XmlDeserializable { /** * An array with requested properties. * * @var array */ public $properties; /** * List of property/component filters. * * @var array */ public $filters; /** * @var int */ public $limit; /** * @var int */ public $offset; /** * The deserialize method is called during xml parsing. * * This method is called statically, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $elems = $reader->parseInnerTree([ '{http://nextcloud.com/ns}comp-filter' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\CompFilter', '{http://nextcloud.com/ns}prop-filter' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter', '{http://nextcloud.com/ns}param-filter' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\ParamFilter', '{http://nextcloud.com/ns}search-term' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter', '{http://nextcloud.com/ns}limit' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\LimitFilter', '{http://nextcloud.com/ns}offset' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\OffsetFilter', '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', ]); $newProps = [ 'filters' => [], 'properties' => [], 'limit' => null, 'offset' => null ]; if (!is_array($elems)) { $elems = []; } foreach ($elems as $elem) { switch ($elem['name']) { case '{DAV:}prop': $newProps['properties'] = array_keys($elem['value']); break; case '{' . SearchPlugin::NS_Nextcloud . '}filter': foreach ($elem['value'] as $subElem) { if ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}comp-filter') { if (!isset($newProps['filters']['comps']) || !is_array($newProps['filters']['comps'])) { $newProps['filters']['comps'] = []; } $newProps['filters']['comps'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}prop-filter') { if (!isset($newProps['filters']['props']) || !is_array($newProps['filters']['props'])) { $newProps['filters']['props'] = []; } $newProps['filters']['props'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}param-filter') { if (!isset($newProps['filters']['params']) || !is_array($newProps['filters']['params'])) { $newProps['filters']['params'] = []; } $newProps['filters']['params'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}search-term') { $newProps['filters']['search-term'] = $subElem['value']; } } break; case '{' . SearchPlugin::NS_Nextcloud . '}limit': $newProps['limit'] = $elem['value']; break; case '{' . SearchPlugin::NS_Nextcloud . '}offset': $newProps['offset'] = $elem['value']; break; } } if (empty($newProps['filters'])) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}filter element is required for this request'); } $propsOrParamsDefined = (!empty($newProps['filters']['props']) || !empty($newProps['filters']['params'])); $noCompsDefined = empty($newProps['filters']['comps']); if ($propsOrParamsDefined && $noCompsDefined) { throw new BadRequest('{' . SearchPlugin::NS_Nextcloud . '}prop-filter or {' . SearchPlugin::NS_Nextcloud . '}param-filter given without any {' . SearchPlugin::NS_Nextcloud . '}comp-filter'); } if (!isset($newProps['filters']['search-term'])) { throw new BadRequest('{' . SearchPlugin::NS_Nextcloud . '}search-term is required for this request'); } if (empty($newProps['filters']['props']) && empty($newProps['filters']['params'])) { throw new BadRequest('At least one{' . SearchPlugin::NS_Nextcloud . '}prop-filter or {' . SearchPlugin::NS_Nextcloud . '}param-filter is required for this request'); } $obj = new self(); foreach ($newProps as $key => $value) { $obj->$key = $value; } return $obj; } } lib/CalDAV/Search/Xml/Filter/OffsetFilter.php 0000604 00000002531 15247100403 0014634 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class OffsetFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return int */ static function xmlDeserialize(Reader $reader) { $value = $reader->parseInnerTree(); if (!is_int($value) && !is_string($value)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}offset has illegal value'); } return intval($value); } } lib/CalDAV/Search/Xml/Filter/PropFilter.php 0000604 00000002642 15247100403 0014331 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class PropFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $att = $reader->parseAttributes(); $componentName = $att['name']; $reader->parseInnerTree(); if (!is_string($componentName)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}prop-filter requires a valid name attribute'); } return $componentName; } } lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php 0000604 00000002512 15247100403 0015442 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class SearchTermFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $value = $reader->parseInnerTree(); if (!is_string($value)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}search-term has illegal value'); } return $value; } } lib/CalDAV/Search/Xml/Filter/CompFilter.php 0000604 00000002642 15247100403 0014307 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class CompFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $att = $reader->parseAttributes(); $componentName = $att['name']; $reader->parseInnerTree(); if (!is_string($componentName)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}comp-filter requires a valid name attribute'); } return $componentName; } } lib/CalDAV/Search/Xml/Filter/LimitFilter.php 0000604 00000002527 15247100403 0014471 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class LimitFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return int */ static function xmlDeserialize(Reader $reader) { $value = $reader->parseInnerTree(); if (!is_int($value) && !is_string($value)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}limit has illegal value'); } return intval($value); } } lib/CalDAV/Search/Xml/Filter/ParamFilter.php 0000604 00000003213 15247100403 0014444 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class ParamFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $att = $reader->parseAttributes(); $property = $att['property']; $parameter = $att['name']; $reader->parseInnerTree(); if (!is_string($property)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}param-filter requires a valid property attribute'); } if (!is_string($parameter)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}param-filter requires a valid parameter attribute'); } return [ 'property' => $property, 'parameter' => $parameter, ]; } } lib/CalDAV/Search/SearchPlugin.php 0000604 00000010514 15247100403 0012637 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use OCA\DAV\CalDAV\CalendarHome; class SearchPlugin extends ServerPlugin { const NS_Nextcloud = 'http://nextcloud.com/ns'; /** * Reference to SabreDAV server object. * * @var \Sabre\DAV\Server */ protected $server; /** * This method should return a list of server-features. * * This is for example 'versioning' and is added to the DAV: header * in an OPTIONS response. * * @return string[] */ public function getFeatures() { // May have to be changed to be detected return ['nc-calendar-search']; } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using Sabre\DAV\Server::getPlugin * * @return string */ public function getPluginName() { return 'nc-calendar-search'; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server */ public function initialize(Server $server) { $this->server = $server; $server->on('report', [$this, 'report']); $server->xml->elementMap['{' . self::NS_Nextcloud . '}calendar-search'] = 'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport'; } /** * This functions handles REPORT requests specific to CalDAV * * @param string $reportName * @param mixed $report * @param mixed $path * @return bool */ public function report($reportName, $report, $path) { switch ($reportName) { case '{' . self::NS_Nextcloud . '}calendar-search': $this->server->transactionType = 'report-nc-calendar-search'; $this->calendarSearch($report); return false; } } /** * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. * Note that you still need to subscribe to the 'report' event to actually * implement them * * @param string $uri * @return array */ public function getSupportedReportSet($uri) { $node = $this->server->tree->getNodeForPath($uri); $reports = []; if ($node instanceof CalendarHome) { $reports[] = '{' . self::NS_Nextcloud . '}calendar-search'; } return $reports; } /** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param Xml\Request\CalendarSearchReport $report * @return void */ private function calendarSearch($report) { $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(2); // The default result is an empty array $result = []; // If we're dealing with the calendar home, the calendar home itself is // responsible for the calendar-query if ($node instanceof CalendarHome && $depth == 2) { $nodePaths = $node->calendarSearch($report->filters, $report->limit, $report->offset); foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath( $this->server->getRequestUri() . '/' . $path, $report->properties); $result[] = $properties; } } $prefer = $this->server->getHTTPPrefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody( $this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); } } lib/CalDAV/Schedule/IMipPlugin.php 0000604 00000013520 15247100403 0012617 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017, Georg Ehrke * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Schedule; use OCP\AppFramework\Utility\ITimeFactory; use OCP\ILogger; use OCP\Mail\IMailer; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\DateTimeParser; use Sabre\VObject\ITip; use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin; use Sabre\VObject\Recur\EventIterator; /** * iMIP handler. * * This class is responsible for sending out iMIP messages. iMIP is the * email-based transport for iTIP. iTIP deals with scheduling operations for * iCalendar objects. * * If you want to customize the email that gets sent out, you can do so by * extending this class and overriding the sendMessage method. * * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class IMipPlugin extends SabreIMipPlugin { /** @var IMailer */ private $mailer; /** @var ILogger */ private $logger; /** @var ITimeFactory */ private $timeFactory; const MAX_DATE = '2038-01-01'; /** * Creates the email handler. * * @param IMailer $mailer * @param ILogger $logger * @param ITimeFactory $timeFactory */ function __construct(IMailer $mailer, ILogger $logger, ITimeFactory $timeFactory) { parent::__construct(''); $this->mailer = $mailer; $this->logger = $logger; $this->timeFactory = $timeFactory; } /** * Event handler for the 'schedule' event. * * @param ITip\Message $iTipMessage * @return void */ function schedule(ITip\Message $iTipMessage) { // Not sending any emails if the system considers the update // insignificant. if (!$iTipMessage->significantChange) { if (!$iTipMessage->scheduleStatus) { $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; } return; } $summary = $iTipMessage->message->VEVENT->SUMMARY; if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') { return; } if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') { return; } // don't send out mails for events that already took place if ($this->isEventInThePast($iTipMessage->message)) { return; } $sender = substr($iTipMessage->sender, 7); $recipient = substr($iTipMessage->recipient, 7); $senderName = ($iTipMessage->senderName) ? $iTipMessage->senderName : null; $recipientName = ($iTipMessage->recipientName) ? $iTipMessage->recipientName : null; $subject = 'SabreDAV iTIP message'; switch (strtoupper($iTipMessage->method)) { case 'REPLY' : $subject = 'Re: ' . $summary; break; case 'REQUEST' : $subject = $summary; break; case 'CANCEL' : $subject = 'Cancelled: ' . $summary; break; } $contentType = 'text/calendar; charset=UTF-8; method=' . $iTipMessage->method; $message = $this->mailer->createMessage(); $message->setReplyTo([$sender => $senderName]) ->setTo([$recipient => $recipientName]) ->setSubject($subject) ->setBody($iTipMessage->message->serialize(), $contentType); try { $failed = $this->mailer->send($message); if ($failed) { $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; } $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; } catch(\Exception $ex) { $this->logger->logException($ex, ['app' => 'dav']); $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; } } /** * check if event took place in the past already * @param VCalendar $vObject * @return bool */ private function isEventInThePast(VCalendar $vObject) { $component = $vObject->VEVENT; $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); // $component->DTEND->getDateTime() returns DateTimeImmutable $endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); $lastOccurrence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); // $component->DTSTART->getDateTime() returns DateTimeImmutable $endDate = $endDate->modify('+1 day'); $lastOccurrence = $endDate->getTimeStamp(); } else { $lastOccurrence = $firstOccurrence; } } else { $it = new EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { $lastOccurrence = $maxDate->getTimestamp(); } else { $end = $it->getDtEnd(); while($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurrence = $end->getTimestamp(); } } $currentTime = $this->timeFactory->getTime(); return $lastOccurrence < $currentTime; } } lib/CalDAV/Schedule/Plugin.php 0000604 00000006053 15247100403 0012043 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * @copyright Copyright (c) 2016, Joas Schilling <coding@schilljs.com> * * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Schedule; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarHome; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\Xml\Property\LocalHref; use Sabre\DAVACL\IPrincipal; class Plugin extends \Sabre\CalDAV\Schedule\Plugin { /** * Initializes the plugin * * @param Server $server * @return void */ function initialize(Server $server) { parent::initialize($server); $server->on('propFind', [$this, 'propFindDefaultCalendarUrl'], 90); } /** * Returns a list of addresses that are associated with a principal. * * @param string $principal * @return array */ protected function getAddressesForPrincipal($principal) { $result = parent::getAddressesForPrincipal($principal); if ($result === null) { $result = []; } return $result; } /** * Always use the personal calendar as target for scheduled events * * @param PropFind $propFind * @param INode $node * @return void */ function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) { if ($node instanceof IPrincipal) { $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($node) { /** @var \OCA\DAV\CalDAV\Plugin $caldavPlugin */ $caldavPlugin = $this->server->getPlugin('caldav'); $principalUrl = $node->getPrincipalUrl(); $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); if (!$calendarHomePath) { return null; } /** @var CalendarHome $calendarHome */ $calendarHome = $this->server->tree->getNodeForPath($calendarHomePath); if (!$calendarHome->childExists(CalDavBackend::PERSONAL_CALENDAR_URI)) { $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, CalDavBackend::PERSONAL_CALENDAR_URI, [ '{DAV:}displayname' => CalDavBackend::PERSONAL_CALENDAR_NAME, ]); } $result = $this->server->getPropertiesForPath($calendarHomePath . '/' . CalDavBackend::PERSONAL_CALENDAR_URI, [], 1); if (empty($result)) { return null; } return new LocalHref($result[0]['href']); }); } } } lib/CalDAV/PublicCalendarRoot.php 0000604 00000002645 15247100403 0012570 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud, Inc. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\DAV\Collection; class PublicCalendarRoot extends Collection { /** @var CalDavBackend */ protected $caldavBackend; /** @var \OCP\IL10N */ protected $l10n; function __construct(CalDavBackend $caldavBackend) { $this->caldavBackend = $caldavBackend; $this->l10n = \OC::$server->getL10N('dav'); } /** * @inheritdoc */ function getName() { return 'public-calendars'; } /** * @inheritdoc */ function getChild($name) { $calendar = $this->caldavBackend->getPublicCalendar($name); return new PublicCalendar($this->caldavBackend, $calendar, $this->l10n); } /** * @inheritdoc */ function getChildren() { return []; } } lib/CalDAV/Plugin.php 0000604 00000002202 15247100403 0010277 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud GmbH. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\HTTP\URLUtil; class Plugin extends \Sabre\CalDAV\Plugin { /** * @inheritdoc */ function getCalendarHomeForPrincipal($principalUrl) { if (strrpos($principalUrl, 'principals/users', -strlen($principalUrl)) !== false) { list(, $principalId) = URLUtil::splitPath($principalUrl); return self::CALENDAR_ROOT .'/' . $principalId; } return; } } lib/CalDAV/PublicCalendar.php 0000604 00000004664 15247100403 0011727 0 ustar 00 <?php /** * @copyright Copyright (c) 2017, Georg Ehrke * * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\DAV\Exception\NotFound; class PublicCalendar extends Calendar { /** * @param string $name * @throws NotFound * @return PublicCalendarObject */ public function getChild($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); if (!$obj) { throw new NotFound('Calendar object not found'); } if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { throw new NotFound('Calendar object not found'); } $obj['acl'] = $this->getChildACL(); return new PublicCalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } /** * @return PublicCalendarObject[] */ public function getChildren() { $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new PublicCalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } /** * @param string[] $paths * @return PublicCalendarObject[] */ public function getMultipleChildren(array $paths) { $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new PublicCalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } /** * public calendars are always shared * @return bool */ protected function isShared() { return true; } } lib/CalDAV/Activity/Filter/Todo.php 0000604 00000004475 15247100403 0013005 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Filter; use OCP\Activity\IFilter; use OCP\IL10N; use OCP\IURLGenerator; class Todo implements IFilter { /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar_todo'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('Todos'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 40; } /** * @return string Full URL to an icon, empty string when none is given * @since 11.0.0 */ public function getIcon() { return $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg')); } /** * @param string[] $types * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function filterTypes(array $types) { return array_intersect(['calendar_todo'], $types); } /** * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function allowedApps() { return []; } } lib/CalDAV/Activity/Filter/Calendar.php 0000604 00000004517 15247100403 0013606 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Filter; use OCP\Activity\IFilter; use OCP\IL10N; use OCP\IURLGenerator; class Calendar implements IFilter { /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('Calendar'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 40; } /** * @return string Full URL to an icon, empty string when none is given * @since 11.0.0 */ public function getIcon() { return $this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg')); } /** * @param string[] $types * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function filterTypes(array $types) { return array_intersect(['calendar', 'calendar_event'], $types); } /** * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function allowedApps() { return []; } } lib/CalDAV/Activity/Provider/Todo.php 0000604 00000011142 15247100403 0013337 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; class Todo extends Event { /** * @param string $language * @param IEvent $event * @param IEvent|null $previousEvent * @return IEvent * @throws \InvalidArgumentException * @since 11.0.0 */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'dav' || $event->getType() !== 'calendar_todo') { throw new \InvalidArgumentException(); } $this->l = $this->languageFactory->get('dav', $language); if ($this->activityManager->getRequirePNG()) { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.png'))); } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg'))); } if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_todo') { $subject = $this->l->t('{actor} created todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_todo_self') { $subject = $this->l->t('You created todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_todo') { $subject = $this->l->t('{actor} deleted todo {todo} from list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_todo_self') { $subject = $this->l->t('You deleted todo {todo} from list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo') { $subject = $this->l->t('{actor} updated todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_self') { $subject = $this->l->t('You updated todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_completed') { $subject = $this->l->t('{actor} solved todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self') { $subject = $this->l->t('You solved todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action') { $subject = $this->l->t('{actor} reopened todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self') { $subject = $this->l->t('You reopened todo {todo} in list {calendar}'); } else { throw new \InvalidArgumentException(); } $parsedParameters = $this->getParameters($event); $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('todo', $event, $previousEvent); return $event; } /** * @param IEvent $event * @return array */ protected function getParameters(IEvent $event) { $subject = $event->getSubject(); $parameters = $event->getSubjectParameters(); switch ($subject) { case self::SUBJECT_OBJECT_ADD . '_todo': case self::SUBJECT_OBJECT_DELETE . '_todo': case self::SUBJECT_OBJECT_UPDATE . '_todo': case self::SUBJECT_OBJECT_UPDATE . '_todo_completed': case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action': return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'todo' => $this->generateObjectParameter($parameters[2]), ]; case self::SUBJECT_OBJECT_ADD . '_todo_self': case self::SUBJECT_OBJECT_DELETE . '_todo_self': case self::SUBJECT_OBJECT_UPDATE . '_todo_self': case self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self': case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self': return [ 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'todo' => $this->generateObjectParameter($parameters[2]), ]; } throw new \InvalidArgumentException(); } } lib/CalDAV/Activity/Provider/Calendar.php 0000604 00000017451 15247100403 0014154 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; use OCP\Activity\IEventMerger; use OCP\Activity\IManager; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\L10N\IFactory; class Calendar extends Base { const SUBJECT_ADD = 'calendar_add'; const SUBJECT_UPDATE = 'calendar_update'; const SUBJECT_DELETE = 'calendar_delete'; const SUBJECT_SHARE_USER = 'calendar_user_share'; const SUBJECT_SHARE_GROUP = 'calendar_group_share'; const SUBJECT_UNSHARE_USER = 'calendar_user_unshare'; const SUBJECT_UNSHARE_GROUP = 'calendar_group_unshare'; /** @var IFactory */ protected $languageFactory; /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; /** @var IManager */ protected $activityManager; /** @var IEventMerger */ protected $eventMerger; /** * @param IFactory $languageFactory * @param IURLGenerator $url * @param IManager $activityManager * @param IUserManager $userManager * @param IEventMerger $eventMerger */ public function __construct(IFactory $languageFactory, IURLGenerator $url, IManager $activityManager, IUserManager $userManager, IEventMerger $eventMerger) { parent::__construct($userManager); $this->languageFactory = $languageFactory; $this->url = $url; $this->activityManager = $activityManager; $this->eventMerger = $eventMerger; } /** * @param string $language * @param IEvent $event * @param IEvent|null $previousEvent * @return IEvent * @throws \InvalidArgumentException * @since 11.0.0 */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'dav' || $event->getType() !== 'calendar') { throw new \InvalidArgumentException(); } $this->l = $this->languageFactory->get('dav', $language); if ($this->activityManager->getRequirePNG()) { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.png'))); } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg'))); } if ($event->getSubject() === self::SUBJECT_ADD) { $subject = $this->l->t('{actor} created calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_ADD . '_self') { $subject = $this->l->t('You created calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_DELETE) { $subject = $this->l->t('{actor} deleted calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_DELETE . '_self') { $subject = $this->l->t('You deleted calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_UPDATE) { $subject = $this->l->t('{actor} updated calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_UPDATE . '_self') { $subject = $this->l->t('You updated calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_SHARE_USER) { $subject = $this->l->t('{actor} shared calendar {calendar} with you'); } else if ($event->getSubject() === self::SUBJECT_SHARE_USER . '_you') { $subject = $this->l->t('You shared calendar {calendar} with {user}'); } else if ($event->getSubject() === self::SUBJECT_SHARE_USER . '_by') { $subject = $this->l->t('{actor} shared calendar {calendar} with {user}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER) { $subject = $this->l->t('{actor} unshared calendar {calendar} from you'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER . '_you') { $subject = $this->l->t('You unshared calendar {calendar} from {user}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER . '_by') { $subject = $this->l->t('{actor} unshared calendar {calendar} from {user}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER . '_self') { $subject = $this->l->t('{actor} unshared calendar {calendar} from themselves'); } else if ($event->getSubject() === self::SUBJECT_SHARE_GROUP . '_you') { $subject = $this->l->t('You shared calendar {calendar} with group {group}'); } else if ($event->getSubject() === self::SUBJECT_SHARE_GROUP . '_by') { $subject = $this->l->t('{actor} shared calendar {calendar} with group {group}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_GROUP . '_you') { $subject = $this->l->t('You unshared calendar {calendar} from group {group}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_GROUP . '_by') { $subject = $this->l->t('{actor} unshared calendar {calendar} from group {group}'); } else { throw new \InvalidArgumentException(); } $parsedParameters = $this->getParameters($event); $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('calendar', $event, $previousEvent); if ($event->getChildEvent() === null) { if (isset($parsedParameters['user'])) { // Couldn't group by calendar, maybe we can group by users $event = $this->eventMerger->mergeEvents('user', $event, $previousEvent); } else if (isset($parsedParameters['group'])) { // Couldn't group by calendar, maybe we can group by groups $event = $this->eventMerger->mergeEvents('group', $event, $previousEvent); } } return $event; } /** * @param IEvent $event * @return array */ protected function getParameters(IEvent $event) { $subject = $event->getSubject(); $parameters = $event->getSubjectParameters(); switch ($subject) { case self::SUBJECT_ADD: case self::SUBJECT_ADD . '_self': case self::SUBJECT_DELETE: case self::SUBJECT_DELETE . '_self': case self::SUBJECT_UPDATE: case self::SUBJECT_UPDATE . '_self': case self::SUBJECT_SHARE_USER: case self::SUBJECT_UNSHARE_USER: case self::SUBJECT_UNSHARE_USER . '_self': return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), ]; case self::SUBJECT_SHARE_USER . '_you': case self::SUBJECT_UNSHARE_USER . '_you': return [ 'user' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), ]; case self::SUBJECT_SHARE_USER . '_by': case self::SUBJECT_UNSHARE_USER . '_by': return [ 'user' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'actor' => $this->generateUserParameter($parameters[2]), ]; case self::SUBJECT_SHARE_GROUP . '_you': case self::SUBJECT_UNSHARE_GROUP . '_you': return [ 'group' => $this->generateGroupParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), ]; case self::SUBJECT_SHARE_GROUP . '_by': case self::SUBJECT_UNSHARE_GROUP . '_by': return [ 'group' => $this->generateGroupParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'actor' => $this->generateUserParameter($parameters[2]), ]; } throw new \InvalidArgumentException(); } } lib/CalDAV/Activity/Provider/Base.php 0000604 00000006022 15247100403 0013305 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; use OCP\Activity\IProvider; use OCP\IUser; use OCP\IUserManager; abstract class Base implements IProvider { /** @var IUserManager */ protected $userManager; /** @var string[] cached displayNames - key is the UID and value the displayname */ protected $displayNames = []; /** * @param IUserManager $userManager */ public function __construct(IUserManager $userManager) { $this->userManager = $userManager; } /** * @param IEvent $event * @param string $subject * @param array $parameters */ protected function setSubjects(IEvent $event, $subject, array $parameters) { $placeholders = $replacements = []; foreach ($parameters as $placeholder => $parameter) { $placeholders[] = '{' . $placeholder . '}'; $replacements[] = $parameter['name']; } $event->setParsedSubject(str_replace($placeholders, $replacements, $subject)) ->setRichSubject($subject, $parameters); } /** * @param array $eventData * @return array */ protected function generateObjectParameter($eventData) { if (!is_array($eventData) || !isset($eventData['id']) || !isset($eventData['name'])) { throw new \InvalidArgumentException(); }; return [ 'type' => 'calendar-event', 'id' => $eventData['id'], 'name' => $eventData['name'], ]; } /** * @param int $id * @param string $name * @return array */ protected function generateCalendarParameter($id, $name) { return [ 'type' => 'calendar', 'id' => $id, 'name' => $name, ]; } /** * @param string $id * @return array */ protected function generateGroupParameter($id) { return [ 'type' => 'group', 'id' => $id, 'name' => $id, ]; } /** * @param string $uid * @return array */ protected function generateUserParameter($uid) { if (!isset($this->displayNames[$uid])) { $this->displayNames[$uid] = $this->getDisplayName($uid); } return [ 'type' => 'user', 'id' => $uid, 'name' => $this->displayNames[$uid], ]; } /** * @param string $uid * @return string */ protected function getDisplayName($uid) { $user = $this->userManager->get($uid); if ($user instanceof IUser) { return $user->getDisplayName(); } else { return $uid; } } } lib/CalDAV/Activity/Provider/Event.php 0000604 00000011443 15247100403 0013517 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; use OCP\Activity\IEventMerger; use OCP\Activity\IManager; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\L10N\IFactory; class Event extends Base { const SUBJECT_OBJECT_ADD = 'object_add'; const SUBJECT_OBJECT_UPDATE = 'object_update'; const SUBJECT_OBJECT_DELETE = 'object_delete'; /** @var IFactory */ protected $languageFactory; /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; /** @var IManager */ protected $activityManager; /** @var IEventMerger */ protected $eventMerger; /** * @param IFactory $languageFactory * @param IURLGenerator $url * @param IManager $activityManager * @param IUserManager $userManager * @param IEventMerger $eventMerger */ public function __construct(IFactory $languageFactory, IURLGenerator $url, IManager $activityManager, IUserManager $userManager, IEventMerger $eventMerger) { parent::__construct($userManager); $this->languageFactory = $languageFactory; $this->url = $url; $this->activityManager = $activityManager; $this->eventMerger = $eventMerger; } /** * @param string $language * @param IEvent $event * @param IEvent|null $previousEvent * @return IEvent * @throws \InvalidArgumentException * @since 11.0.0 */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'dav' || $event->getType() !== 'calendar_event') { throw new \InvalidArgumentException(); } $this->l = $this->languageFactory->get('dav', $language); if ($this->activityManager->getRequirePNG()) { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.png'))); } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg'))); } if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_event') { $subject = $this->l->t('{actor} created event {event} in calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_event_self') { $subject = $this->l->t('You created event {event} in calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_event') { $subject = $this->l->t('{actor} deleted event {event} from calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_event_self') { $subject = $this->l->t('You deleted event {event} from calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_event') { $subject = $this->l->t('{actor} updated event {event} in calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_event_self') { $subject = $this->l->t('You updated event {event} in calendar {calendar}'); } else { throw new \InvalidArgumentException(); } $parsedParameters = $this->getParameters($event); $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('event', $event, $previousEvent); return $event; } /** * @param IEvent $event * @return array */ protected function getParameters(IEvent $event) { $subject = $event->getSubject(); $parameters = $event->getSubjectParameters(); switch ($subject) { case self::SUBJECT_OBJECT_ADD . '_event': case self::SUBJECT_OBJECT_DELETE . '_event': case self::SUBJECT_OBJECT_UPDATE . '_event': return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'event' => $this->generateObjectParameter($parameters[2]), ]; case self::SUBJECT_OBJECT_ADD . '_event_self': case self::SUBJECT_OBJECT_DELETE . '_event_self': case self::SUBJECT_OBJECT_UPDATE . '_event_self': return [ 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'event' => $this->generateObjectParameter($parameters[2]), ]; } throw new \InvalidArgumentException(); } } lib/CalDAV/Activity/Setting/Event.php 0000604 00000004411 15247100403 0013337 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Setting; use OCP\Activity\ISetting; use OCP\IL10N; class Event implements ISetting { /** @var IL10N */ protected $l; /** * @param IL10N $l */ public function __construct(IL10N $l) { $this->l = $l; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar_event'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('A calendar <strong>event</strong> was modified'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 50; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function canChangeStream() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledStream() { return true; } /** * @return bool True when the option can be changed for the mail * @since 11.0.0 */ public function canChangeMail() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledMail() { return false; } } lib/CalDAV/Activity/Setting/Todo.php 0000604 00000004406 15247100403 0013167 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Setting; use OCP\Activity\ISetting; use OCP\IL10N; class Todo implements ISetting { /** @var IL10N */ protected $l; /** * @param IL10N $l */ public function __construct(IL10N $l) { $this->l = $l; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar_todo'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('A calendar <strong>todo</strong> was modified'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 50; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function canChangeStream() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledStream() { return true; } /** * @return bool True when the option can be changed for the mail * @since 11.0.0 */ public function canChangeMail() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledMail() { return false; } } lib/CalDAV/Activity/Setting/Calendar.php 0000604 00000004400 15247100403 0013765 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Setting; use OCP\Activity\ISetting; use OCP\IL10N; class Calendar implements ISetting { /** @var IL10N */ protected $l; /** * @param IL10N $l */ public function __construct(IL10N $l) { $this->l = $l; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('A <strong>calendar</strong> was modified'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 50; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function canChangeStream() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledStream() { return true; } /** * @return bool True when the option can be changed for the mail * @since 11.0.0 */ public function canChangeMail() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledMail() { return false; } } lib/CalDAV/Activity/Backend.php 0000604 00000032017 15247100403 0012173 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity; use OCA\DAV\CalDAV\Activity\Provider\Calendar; use OCA\DAV\CalDAV\Activity\Provider\Event; use OCP\Activity\IEvent; use OCP\Activity\IManager as IActivityManager; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; use Sabre\VObject\Reader; /** * Class Backend * * @package OCA\DAV\CalDAV\Activity */ class Backend { /** @var IActivityManager */ protected $activityManager; /** @var IGroupManager */ protected $groupManager; /** @var IUserSession */ protected $userSession; /** * @param IActivityManager $activityManager * @param IGroupManager $groupManager * @param IUserSession $userSession */ public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession) { $this->activityManager = $activityManager; $this->groupManager = $groupManager; $this->userSession = $userSession; } /** * Creates activities when a calendar was creates * * @param array $calendarData */ public function onCalendarAdd(array $calendarData) { $this->triggerCalendarActivity(Calendar::SUBJECT_ADD, $calendarData); } /** * Creates activities when a calendar was updated * * @param array $calendarData * @param array $shares * @param array $properties */ public function onCalendarUpdate(array $calendarData, array $shares, array $properties) { $this->triggerCalendarActivity(Calendar::SUBJECT_UPDATE, $calendarData, $shares, $properties); } /** * Creates activities when a calendar was deleted * * @param array $calendarData * @param array $shares */ public function onCalendarDelete(array $calendarData, array $shares) { $this->triggerCalendarActivity(Calendar::SUBJECT_DELETE, $calendarData, $shares); } /** * Creates activities for all related users when a calendar was touched * * @param string $action * @param array $calendarData * @param array $shares * @param array $changedProperties */ protected function triggerCalendarActivity($action, array $calendarData, array $shares = [], array $changedProperties = []) { if (!isset($calendarData['principaluri'])) { return; } $principal = explode('/', $calendarData['principaluri']); $owner = array_pop($principal); $currentUser = $this->userSession->getUser(); if ($currentUser instanceof IUser) { $currentUser = $currentUser->getUID(); } else { $currentUser = $owner; } $event = $this->activityManager->generateEvent(); $event->setApp('dav') ->setObject('calendar', (int) $calendarData['id']) ->setType('calendar') ->setAuthor($currentUser); $changedVisibleInformation = array_intersect([ '{DAV:}displayname', '{http://apple.com/ns/ical/}calendar-color' ], array_keys($changedProperties)); if (empty($shares) || ($action === Calendar::SUBJECT_UPDATE && empty($changedVisibleInformation))) { $users = [$owner]; } else { $users = $this->getUsersForShares($shares); $users[] = $owner; } foreach ($users as $user) { $event->setAffectedUser($user) ->setSubject( $user === $currentUser ? $action . '_self' : $action, [ $currentUser, $calendarData['{DAV:}displayname'], ] ); $this->activityManager->publish($event); } } /** * Creates activities for all related users when a calendar was (un-)shared * * @param array $calendarData * @param array $shares * @param array $add * @param array $remove */ public function onCalendarUpdateShares(array $calendarData, array $shares, array $add, array $remove) { $principal = explode('/', $calendarData['principaluri']); $owner = $principal[2]; $currentUser = $this->userSession->getUser(); if ($currentUser instanceof IUser) { $currentUser = $currentUser->getUID(); } else { $currentUser = $owner; } $event = $this->activityManager->generateEvent(); $event->setApp('dav') ->setObject('calendar', (int) $calendarData['id']) ->setType('calendar') ->setAuthor($currentUser); foreach ($remove as $principal) { // principal:principals/users/test $parts = explode(':', $principal, 2); if ($parts[0] !== 'principal') { continue; } $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { $this->triggerActivityUser( $principal[2], $event, $calendarData, Calendar::SUBJECT_UNSHARE_USER, Calendar::SUBJECT_DELETE . '_self' ); if ($owner !== $principal[2]) { $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_UNSHARE_USER . '_you'; } else if ($principal[2] === $event->getAuthor()) { $subject = Calendar::SUBJECT_UNSHARE_USER . '_self'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_UNSHARE_USER . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_UNSHARE_USER . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } else if ($principal[1] === 'groups') { $this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_UNSHARE_USER); $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_UNSHARE_GROUP . '_you'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_UNSHARE_GROUP . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_UNSHARE_GROUP . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } foreach ($add as $share) { if ($this->isAlreadyShared($share['href'], $shares)) { continue; } // principal:principals/users/test $parts = explode(':', $share['href'], 2); if ($parts[0] !== 'principal') { continue; } $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { $this->triggerActivityUser($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER); if ($owner !== $principal[2]) { $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_SHARE_USER . '_you'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_SHARE_USER . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_SHARE_USER . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } else if ($principal[1] === 'groups') { $this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER); $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_SHARE_GROUP . '_you'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_SHARE_GROUP . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_SHARE_GROUP . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } } /** * Checks if a calendar is already shared with a principal * * @param string $principal * @param array[] $shares * @return bool */ protected function isAlreadyShared($principal, $shares) { foreach ($shares as $share) { if ($principal === $share['href']) { return true; } } return false; } /** * Creates the given activity for all members of the given group * * @param string $gid * @param IEvent $event * @param array $properties * @param string $subject */ protected function triggerActivityGroup($gid, IEvent $event, array $properties, $subject) { $group = $this->groupManager->get($gid); if ($group instanceof IGroup) { foreach ($group->getUsers() as $user) { // Exclude current user if ($user->getUID() !== $event->getAuthor()) { $this->triggerActivityUser($user->getUID(), $event, $properties, $subject); } } } } /** * Creates the given activity for the given user * * @param string $user * @param IEvent $event * @param array $properties * @param string $subject * @param string $subjectSelf */ protected function triggerActivityUser($user, IEvent $event, array $properties, $subject, $subjectSelf = '') { $event->setAffectedUser($user) ->setSubject( $user === $event->getAuthor() && $subjectSelf ? $subjectSelf : $subject, [ $event->getAuthor(), $properties['{DAV:}displayname'], ] ); $this->activityManager->publish($event); } /** * Creates activities when a calendar object was created/updated/deleted * * @param string $action * @param array $calendarData * @param array $shares * @param array $objectData */ public function onTouchCalendarObject($action, array $calendarData, array $shares, array $objectData) { if (!isset($calendarData['principaluri'])) { return; } $principal = explode('/', $calendarData['principaluri']); $owner = array_pop($principal); $currentUser = $this->userSession->getUser(); if ($currentUser instanceof IUser) { $currentUser = $currentUser->getUID(); } else { $currentUser = $owner; } $object = $this->getObjectNameAndType($objectData); $action = $action . '_' . $object['type']; if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') { $action .= '_completed'; } else if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'NEEDS-ACTION') { $action .= '_needs_action'; } $event = $this->activityManager->generateEvent(); $event->setApp('dav') ->setObject('calendar', (int) $calendarData['id']) ->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo') ->setAuthor($currentUser); $users = $this->getUsersForShares($shares); $users[] = $owner; foreach ($users as $user) { $event->setAffectedUser($user) ->setSubject( $user === $currentUser ? $action . '_self' : $action, [ $currentUser, $calendarData['{DAV:}displayname'], [ 'id' => $object['id'], 'name' => $object['name'], ], ] ); $this->activityManager->publish($event); } } /** * @param array $objectData * @return string[]|bool */ protected function getObjectNameAndType(array $objectData) { $vObject = Reader::read($objectData['calendardata']); $component = $componentType = null; foreach($vObject->getComponents() as $component) { if (in_array($component->name, ['VEVENT', 'VTODO'])) { $componentType = $component->name; break; } } if (!$componentType) { // Calendar objects must have a VEVENT or VTODO component return false; } if ($componentType === 'VEVENT') { return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'event']; } return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'todo', 'status' => (string) $component->STATUS]; } /** * Get all users that have access to a given calendar * * @param array $shares * @return string[] */ protected function getUsersForShares(array $shares) { $users = $groups = []; foreach ($shares as $share) { $prinical = explode('/', $share['{http://owncloud.org/ns}principal']); if ($prinical[1] === 'users') { $users[] = $prinical[2]; } else if ($prinical[1] === 'groups') { $groups[] = $prinical[2]; } } if (!empty($groups)) { foreach ($groups as $gid) { $group = $this->groupManager->get($gid); if ($group instanceof IGroup) { foreach ($group->getUsers() as $user) { $users[] = $user->getUID(); } } } } return array_unique($users); } } lib/CalDAV/CalendarRoot.php 0000604 00000001755 15247100403 0011432 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; class CalendarRoot extends \Sabre\CalDAV\CalendarRoot { function getChildForPrincipal(array $principal) { return new CalendarHome($this->caldavBackend, $principal); } } lib/CalDAV/CalendarHome.php 0000604 00000010005 15247100403 0011363 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\CalDAV\Backend\NotificationSupport; use Sabre\CalDAV\Backend\SchedulingSupport; use Sabre\CalDAV\Backend\SubscriptionSupport; use Sabre\CalDAV\Schedule\Inbox; use Sabre\CalDAV\Schedule\Outbox; use Sabre\CalDAV\Subscriptions\Subscription; use Sabre\DAV\Exception\NotFound; class CalendarHome extends \Sabre\CalDAV\CalendarHome { /** @var \OCP\IL10N */ private $l10n; public function __construct(BackendInterface $caldavBackend, $principalInfo) { parent::__construct($caldavBackend, $principalInfo); $this->l10n = \OC::$server->getL10N('dav'); } /** * @return BackendInterface */ public function getCalDAVBackend() { return $this->caldavBackend; } /** * @inheritdoc */ function getChildren() { $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); $objects = []; foreach ($calendars as $calendar) { $objects[] = new Calendar($this->caldavBackend, $calendar, $this->l10n); } if ($this->caldavBackend instanceof SchedulingSupport) { $objects[] = new Inbox($this->caldavBackend, $this->principalInfo['uri']); $objects[] = new Outbox($this->principalInfo['uri']); } // We're adding a notifications node, if it's supported by the backend. if ($this->caldavBackend instanceof NotificationSupport) { $objects[] = new \Sabre\CalDAV\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); } // If the backend supports subscriptions, we'll add those as well, if ($this->caldavBackend instanceof SubscriptionSupport) { foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { $objects[] = new Subscription($this->caldavBackend, $subscription); } } return $objects; } /** * @inheritdoc */ function getChild($name) { // Special nodes if ($name === 'inbox' && $this->caldavBackend instanceof SchedulingSupport) { return new Inbox($this->caldavBackend, $this->principalInfo['uri']); } if ($name === 'outbox' && $this->caldavBackend instanceof SchedulingSupport) { return new Outbox($this->principalInfo['uri']); } if ($name === 'notifications' && $this->caldavBackend instanceof NotificationSupport) { return new \Sabre\CalDAv\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); } // Calendars foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { if ($calendar['uri'] === $name) { return new Calendar($this->caldavBackend, $calendar, $this->l10n); } } if ($this->caldavBackend instanceof SubscriptionSupport) { foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { if ($subscription['uri'] === $name) { return new Subscription($this->caldavBackend, $subscription); } } } throw new NotFound('Node with name \'' . $name . '\' could not be found'); } /** * @param array $filters * @param integer|null $limit * @param integer|null $offset */ function calendarSearch(array $filters, $limit=null, $offset=null) { $principalUri = $this->principalInfo['uri']; return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset); } } lib/Capabilities.php 0000604 00000001676 15247100403 0010416 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud GmbH * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV; use OCP\Capabilities\ICapability; class Capabilities implements ICapability { public function getCapabilities() { return [ 'dav' => [ 'chunking' => '1.0', ] ]; } } lib/Comments/EntityCollection.php 0000604 00000011270 15247100403 0013071 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Comments; use OCP\Comments\ICommentsManager; use OCP\Comments\NotFoundException; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\IProperties; use Sabre\DAV\PropPatch; /** * Class EntityCollection * * this represents a specific holder of comments, identified by an entity type * (class member $name) and an entity id (class member $id). * * @package OCA\DAV\Comments */ class EntityCollection extends RootCollection implements IProperties { const PROPERTY_NAME_READ_MARKER = '{http://owncloud.org/ns}readMarker'; /** @var string */ protected $id; /** @var ILogger */ protected $logger; /** * @param string $id * @param string $name * @param ICommentsManager $commentsManager * @param IUserManager $userManager * @param IUserSession $userSession * @param ILogger $logger */ public function __construct( $id, $name, ICommentsManager $commentsManager, IUserManager $userManager, IUserSession $userSession, ILogger $logger ) { foreach(['id', 'name'] as $property) { $$property = trim($$property); if(empty($$property) || !is_string($$property)) { throw new \InvalidArgumentException('"' . $property . '" parameter must be non-empty string'); } } $this->id = $id; $this->name = $name; $this->commentsManager = $commentsManager; $this->logger = $logger; $this->userManager = $userManager; $this->userSession = $userSession; } /** * returns the ID of this entity * * @return string */ public function getId() { return $this->id; } /** * Returns a specific child node, referenced by its name * * This method must throw Sabre\DAV\Exception\NotFound if the node does not * exist. * * @param string $name * @return \Sabre\DAV\INode * @throws NotFound */ function getChild($name) { try { $comment = $this->commentsManager->get($name); return new CommentNode( $this->commentsManager, $comment, $this->userManager, $this->userSession, $this->logger ); } catch (NotFoundException $e) { throw new NotFound(); } } /** * Returns an array with all the child nodes * * @return \Sabre\DAV\INode[] */ function getChildren() { return $this->findChildren(); } /** * Returns an array of comment nodes. Result can be influenced by offset, * limit and date time parameters. * * @param int $limit * @param int $offset * @param \DateTime|null $datetime * @return CommentNode[] */ function findChildren($limit = 0, $offset = 0, \DateTime $datetime = null) { $comments = $this->commentsManager->getForObject($this->name, $this->id, $limit, $offset, $datetime); $result = []; foreach($comments as $comment) { $result[] = new CommentNode( $this->commentsManager, $comment, $this->userManager, $this->userSession, $this->logger ); } return $result; } /** * Checks if a child-node with the specified name exists * * @param string $name * @return bool */ function childExists($name) { try { $this->commentsManager->get($name); return true; } catch (NotFoundException $e) { return false; } } /** * Sets the read marker to the specified date for the logged in user * * @param \DateTime $value * @return bool */ public function setReadMarker($value) { $dateTime = new \DateTime($value); $user = $this->userSession->getUser(); $this->commentsManager->setReadMark($this->name, $this->id, $dateTime, $user); return true; } /** * @inheritdoc */ function propPatch(PropPatch $propPatch) { $propPatch->handle(self::PROPERTY_NAME_READ_MARKER, [$this, 'setReadMarker']); } /** * @inheritdoc */ function getProperties($properties) { $marker = null; $user = $this->userSession->getUser(); if(!is_null($user)) { $marker = $this->commentsManager->getReadMark($this->name, $this->id, $user); } return [self::PROPERTY_NAME_READ_MARKER => $marker]; } } lib/Comments/EntityTypeCollection.php 0000604 00000006267 15247100403 0013745 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Comments; use OCP\Comments\ICommentsManager; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; /** * Class EntityTypeCollection * * This is collection on the type of things a user can leave comments on, for * example: 'files'. * * Its children are instances of EntityCollection (representing a specific * object, for example the file by id). * * @package OCA\DAV\Comments */ class EntityTypeCollection extends RootCollection { /** @var ILogger */ protected $logger; /** @var IUserManager */ protected $userManager; /** @var \Closure */ protected $childExistsFunction; /** * @param string $name * @param ICommentsManager $commentsManager * @param IUserManager $userManager * @param IUserSession $userSession * @param ILogger $logger * @param \Closure $childExistsFunction */ public function __construct( $name, ICommentsManager $commentsManager, IUserManager $userManager, IUserSession $userSession, ILogger $logger, \Closure $childExistsFunction ) { $name = trim($name); if(empty($name) || !is_string($name)) { throw new \InvalidArgumentException('"name" parameter must be non-empty string'); } $this->name = $name; $this->commentsManager = $commentsManager; $this->logger = $logger; $this->userManager = $userManager; $this->userSession = $userSession; $this->childExistsFunction = $childExistsFunction; } /** * Returns a specific child node, referenced by its name * * This method must throw Sabre\DAV\Exception\NotFound if the node does not * exist. * * @param string $name * @return \Sabre\DAV\INode * @throws NotFound */ function getChild($name) { if(!$this->childExists($name)) { throw new NotFound('Entity does not exist or is not available'); } return new EntityCollection( $name, $this->name, $this->commentsManager, $this->userManager, $this->userSession, $this->logger ); } /** * Returns an array with all the child nodes * * @return \Sabre\DAV\INode[] * @throws MethodNotAllowed */ function getChildren() { throw new MethodNotAllowed('No permission to list folder contents'); } /** * Checks if a child-node with the specified name exists * * @param string $name * @return bool */ function childExists($name) { return call_user_func($this->childExistsFunction, $name); } } lib/Comments/RootCollection.php 0000604 00000012012 15247100403 0012533 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Comments; use OCP\Comments\CommentsEntityEvent; use OCP\Comments\ICommentsManager; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; use Sabre\DAV\Exception\NotAuthenticated; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class RootCollection implements ICollection { /** @var EntityTypeCollection[]|null */ private $entityTypeCollections; /** @var ICommentsManager */ protected $commentsManager; /** @var string */ protected $name = 'comments'; /** @var ILogger */ protected $logger; /** @var IUserManager */ protected $userManager; /** @var IUserSession */ protected $userSession; /** @var EventDispatcherInterface */ protected $dispatcher; /** * @param ICommentsManager $commentsManager * @param IUserManager $userManager * @param IUserSession $userSession * @param EventDispatcherInterface $dispatcher * @param ILogger $logger */ public function __construct( ICommentsManager $commentsManager, IUserManager $userManager, IUserSession $userSession, EventDispatcherInterface $dispatcher, ILogger $logger) { $this->commentsManager = $commentsManager; $this->logger = $logger; $this->userManager = $userManager; $this->userSession = $userSession; $this->dispatcher = $dispatcher; } /** * initializes the collection. At this point of time, we need the logged in * user. Since it is not the case when the instance is created, we cannot * have this in the constructor. * * @throws NotAuthenticated */ protected function initCollections() { if($this->entityTypeCollections !== null) { return; } $user = $this->userSession->getUser(); if(is_null($user)) { throw new NotAuthenticated(); } $event = new CommentsEntityEvent(CommentsEntityEvent::EVENT_ENTITY); $this->dispatcher->dispatch(CommentsEntityEvent::EVENT_ENTITY, $event); $this->entityTypeCollections = []; foreach ($event->getEntityCollections() as $entity => $entityExistsFunction) { $this->entityTypeCollections[$entity] = new EntityTypeCollection( $entity, $this->commentsManager, $this->userManager, $this->userSession, $this->logger, $entityExistsFunction ); } } /** * Creates a new file in the directory * * @param string $name Name of the file * @param resource|string $data Initial payload * @return null|string * @throws Forbidden */ function createFile($name, $data = null) { throw new Forbidden('Cannot create comments by id'); } /** * Creates a new subdirectory * * @param string $name * @throws Forbidden */ function createDirectory($name) { throw new Forbidden('Permission denied to create collections'); } /** * Returns a specific child node, referenced by its name * * This method must throw Sabre\DAV\Exception\NotFound if the node does not * exist. * * @param string $name * @return \Sabre\DAV\INode * @throws NotFound */ function getChild($name) { $this->initCollections(); if(isset($this->entityTypeCollections[$name])) { return $this->entityTypeCollections[$name]; } throw new NotFound('Entity type "' . $name . '" not found."'); } /** * Returns an array with all the child nodes * * @return \Sabre\DAV\INode[] */ function getChildren() { $this->initCollections(); return $this->entityTypeCollections; } /** * Checks if a child-node with the specified name exists * * @param string $name * @return bool */ function childExists($name) { $this->initCollections(); return isset($this->entityTypeCollections[$name]); } /** * Deleted the current node * * @throws Forbidden */ function delete() { throw new Forbidden('Permission denied to delete this collection'); } /** * Returns the name of the node. * * This is used to generate the url. * * @return string */ function getName() { return $this->name; } /** * Renames the node * * @param string $name The new name * @throws Forbidden */ function setName($name) { throw new Forbidden('Permission denied to rename this collection'); } /** * Returns the last modification time, as a unix timestamp * * @return int */ function getLastModified() { return null; } } lib/Comments/CommentNode.php 0000604 00000021001 15247100403 0012002 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Comments; use OCP\Comments\IComment; use OCP\Comments\ICommentsManager; use OCP\Comments\MessageTooLongException; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\PropPatch; class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties { const NS_OWNCLOUD = 'http://owncloud.org/ns'; const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}isUnread'; const PROPERTY_NAME_MESSAGE = '{http://owncloud.org/ns}message'; const PROPERTY_NAME_ACTOR_DISPLAYNAME = '{http://owncloud.org/ns}actorDisplayName'; const PROPERTY_NAME_MENTIONS = '{http://owncloud.org/ns}mentions'; const PROPERTY_NAME_MENTION = '{http://owncloud.org/ns}mention'; const PROPERTY_NAME_MENTION_TYPE = '{http://owncloud.org/ns}mentionType'; const PROPERTY_NAME_MENTION_ID = '{http://owncloud.org/ns}mentionId'; const PROPERTY_NAME_MENTION_DISPLAYNAME = '{http://owncloud.org/ns}mentionDisplayName'; /** @var IComment */ public $comment; /** @var ICommentsManager */ protected $commentsManager; /** @var ILogger */ protected $logger; /** @var array list of properties with key being their name and value their setter */ protected $properties = []; /** @var IUserManager */ protected $userManager; /** @var IUserSession */ protected $userSession; /** * CommentNode constructor. * * @param ICommentsManager $commentsManager * @param IComment $comment * @param IUserManager $userManager * @param IUserSession $userSession * @param ILogger $logger */ public function __construct( ICommentsManager $commentsManager, IComment $comment, IUserManager $userManager, IUserSession $userSession, ILogger $logger ) { $this->commentsManager = $commentsManager; $this->comment = $comment; $this->logger = $logger; $methods = get_class_methods($this->comment); $methods = array_filter($methods, function($name){ return strpos($name, 'get') === 0; }); foreach($methods as $getter) { if($getter === 'getMentions') { continue; // special treatment } $name = '{'.self::NS_OWNCLOUD.'}' . lcfirst(substr($getter, 3)); $this->properties[$name] = $getter; } $this->userManager = $userManager; $this->userSession = $userSession; } /** * returns a list of all possible property names * * @return array */ static public function getPropertyNames() { return [ '{http://owncloud.org/ns}id', '{http://owncloud.org/ns}parentId', '{http://owncloud.org/ns}topmostParentId', '{http://owncloud.org/ns}childrenCount', '{http://owncloud.org/ns}verb', '{http://owncloud.org/ns}actorType', '{http://owncloud.org/ns}actorId', '{http://owncloud.org/ns}creationDateTime', '{http://owncloud.org/ns}latestChildDateTime', '{http://owncloud.org/ns}objectType', '{http://owncloud.org/ns}objectId', // re-used property names are defined as constants self::PROPERTY_NAME_MESSAGE, self::PROPERTY_NAME_ACTOR_DISPLAYNAME, self::PROPERTY_NAME_UNREAD, self::PROPERTY_NAME_MENTIONS, self::PROPERTY_NAME_MENTION, self::PROPERTY_NAME_MENTION_TYPE, self::PROPERTY_NAME_MENTION_ID, self::PROPERTY_NAME_MENTION_DISPLAYNAME, ]; } protected function checkWriteAccessOnComment() { $user = $this->userSession->getUser(); if( $this->comment->getActorType() !== 'users' || is_null($user) || $this->comment->getActorId() !== $user->getUID() ) { throw new Forbidden('Only authors are allowed to edit their comment.'); } } /** * Deleted the current node * * @return void */ function delete() { $this->checkWriteAccessOnComment(); $this->commentsManager->delete($this->comment->getId()); } /** * Returns the name of the node. * * This is used to generate the url. * * @return string */ function getName() { return $this->comment->getId(); } /** * Renames the node * * @param string $name The new name * @throws MethodNotAllowed */ function setName($name) { throw new MethodNotAllowed(); } /** * Returns the last modification time, as a unix timestamp * * @return int */ function getLastModified() { return null; } /** * update the comment's message * * @param $propertyValue * @return bool * @throws BadRequest * @throws \Exception */ public function updateComment($propertyValue) { $this->checkWriteAccessOnComment(); try { $this->comment->setMessage($propertyValue); $this->commentsManager->save($this->comment); return true; } catch (\Exception $e) { $this->logger->logException($e, ['app' => 'dav/comments']); if($e instanceof MessageTooLongException) { $msg = 'Message exceeds allowed character limit of '; throw new BadRequest($msg . IComment::MAX_MESSAGE_LENGTH, 0, $e); } throw $e; } } /** * Updates properties on this node. * * This method received a PropPatch object, which contains all the * information about the update. * * To update specific properties, call the 'handle' method on this object. * Read the PropPatch documentation for more information. * * @param PropPatch $propPatch * @return void */ function propPatch(PropPatch $propPatch) { // other properties than 'message' are read only $propPatch->handle(self::PROPERTY_NAME_MESSAGE, [$this, 'updateComment']); } /** * Returns a list of properties for this nodes. * * The properties list is a list of propertynames the client requested, * encoded in clark-notation {xmlnamespace}tagname * * If the array is empty, it means 'all properties' were requested. * * Note that it's fine to liberally give properties back, instead of * conforming to the list of requested properties. * The Server class will filter out the extra. * * @param array $properties * @return array */ function getProperties($properties) { $properties = array_keys($this->properties); $result = []; foreach($properties as $property) { $getter = $this->properties[$property]; if(method_exists($this->comment, $getter)) { $result[$property] = $this->comment->$getter(); } } if($this->comment->getActorType() === 'users') { $user = $this->userManager->get($this->comment->getActorId()); $displayName = is_null($user) ? null : $user->getDisplayName(); $result[self::PROPERTY_NAME_ACTOR_DISPLAYNAME] = $displayName; } $result[self::PROPERTY_NAME_MENTIONS] = $this->composeMentionsPropertyValue(); $unread = null; $user = $this->userSession->getUser(); if(!is_null($user)) { $readUntil = $this->commentsManager->getReadMark( $this->comment->getObjectType(), $this->comment->getObjectId(), $user ); if(is_null($readUntil)) { $unread = 'true'; } else { $unread = $this->comment->getCreationDateTime() > $readUntil; // re-format for output $unread = $unread ? 'true' : 'false'; } } $result[self::PROPERTY_NAME_UNREAD] = $unread; return $result; } /** * transforms a mentions array as returned from IComment->getMentions to an * array with DAV-compatible structure that can be assigned to the * PROPERTY_NAME_MENTION property. * * @return array */ protected function composeMentionsPropertyValue() { return array_map(function($mention) { try { $displayName = $this->commentsManager->resolveDisplayName($mention['type'], $mention['id']); } catch (\OutOfBoundsException $e) { $this->logger->logException($e); // No displayname, upon client's discretion what to display. $displayName = ''; } return [ self::PROPERTY_NAME_MENTION => [ self::PROPERTY_NAME_MENTION_TYPE => $mention['type'], self::PROPERTY_NAME_MENTION_ID => $mention['id'], self::PROPERTY_NAME_MENTION_DISPLAYNAME => $displayName, ] ]; }, $this->comment->getMentions()); } } lib/Comments/CommentsPlugin.php 0000604 00000016441 15247100403 0012552 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Comments; use OCP\Comments\IComment; use OCP\Comments\ICommentsManager; use OCP\IUserSession; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\ReportNotSupported; use Sabre\DAV\Exception\UnsupportedMediaType; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Xml\Element\Response; use Sabre\DAV\Xml\Response\MultiStatus; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\Xml\Writer; /** * Sabre plugin to handle comments: */ class CommentsPlugin extends ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const REPORT_NAME = '{http://owncloud.org/ns}filter-comments'; const REPORT_PARAM_LIMIT = '{http://owncloud.org/ns}limit'; const REPORT_PARAM_OFFSET = '{http://owncloud.org/ns}offset'; const REPORT_PARAM_TIMESTAMP = '{http://owncloud.org/ns}datetime'; /** @var ICommentsManager */ protected $commentsManager; /** @var \Sabre\DAV\Server $server */ private $server; /** @var \OCP\IUserSession */ protected $userSession; /** * Comments plugin * * @param ICommentsManager $commentsManager * @param IUserSession $userSession */ public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) { $this->commentsManager = $commentsManager; $this->userSession = $userSession; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server * @return void */ function initialize(Server $server) { $this->server = $server; if(strpos($this->server->getRequestUri(), 'comments/') !== 0) { return; } $this->server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $this->server->xml->classMap['DateTime'] = function(Writer $writer, \DateTime $value) { $writer->write(\Sabre\HTTP\toDate($value)); }; $this->server->on('report', [$this, 'onReport']); $this->server->on('method:POST', [$this, 'httpPost']); } /** * POST operation on Comments collections * * @param RequestInterface $request request object * @param ResponseInterface $response response object * @return null|false */ public function httpPost(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); $node = $this->server->tree->getNodeForPath($path); if (!$node instanceof EntityCollection) { return null; } $data = $request->getBodyAsString(); $comment = $this->createComment( $node->getName(), $node->getId(), $data, $request->getHeader('Content-Type') ); // update read marker for the current user/poster to avoid // having their own comments marked as unread $node->setReadMarker(null); $url = rtrim($request->getUrl(), '/') . '/' . urlencode($comment->getId()); $response->setHeader('Content-Location', $url); // created $response->setStatus(201); return false; } /** * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. * * @param string $uri * @return array */ public function getSupportedReportSet($uri) { return [self::REPORT_NAME]; } /** * REPORT operations to look for comments * * @param string $reportName * @param array $report * @param string $uri * @return bool * @throws NotFound * @throws ReportNotSupported */ public function onReport($reportName, $report, $uri) { $node = $this->server->tree->getNodeForPath($uri); if(!$node instanceof EntityCollection || $reportName !== self::REPORT_NAME) { throw new ReportNotSupported(); } $args = ['limit' => 0, 'offset' => 0, 'datetime' => null]; $acceptableParameters = [ $this::REPORT_PARAM_LIMIT, $this::REPORT_PARAM_OFFSET, $this::REPORT_PARAM_TIMESTAMP ]; $ns = '{' . $this::NS_OWNCLOUD . '}'; foreach($report as $parameter) { if(!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) { continue; } $args[str_replace($ns, '', $parameter['name'])] = $parameter['value']; } if(!is_null($args['datetime'])) { $args['datetime'] = new \DateTime($args['datetime']); } $results = $node->findChildren($args['limit'], $args['offset'], $args['datetime']); $responses = []; foreach($results as $node) { $nodePath = $this->server->getRequestUri() . '/' . $node->comment->getId(); $resultSet = $this->server->getPropertiesForPath($nodePath, CommentNode::getPropertyNames()); if(isset($resultSet[0]) && isset($resultSet[0][200])) { $responses[] = new Response( $this->server->getBaseUri() . $nodePath, [200 => $resultSet[0][200]], 200 ); } } $xml = $this->server->xml->write( '{DAV:}multistatus', new MultiStatus($responses) ); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setBody($xml); return false; } /** * Creates a new comment * * @param string $objectType e.g. "files" * @param string $objectId e.g. the file id * @param string $data JSON encoded string containing the properties of the tag to create * @param string $contentType content type of the data * @return IComment newly created comment * * @throws BadRequest if a field was missing * @throws UnsupportedMediaType if the content type is not supported */ private function createComment($objectType, $objectId, $data, $contentType = 'application/json') { if (explode(';', $contentType)[0] === 'application/json') { $data = json_decode($data, true); } else { throw new UnsupportedMediaType(); } $actorType = $data['actorType']; $actorId = null; if($actorType === 'users') { $user = $this->userSession->getUser(); if(!is_null($user)) { $actorId = $user->getUID(); } } if(is_null($actorId)) { throw new BadRequest('Invalid actor "' . $actorType .'"'); } try { $comment = $this->commentsManager->create($actorType, $actorId, $objectType, $objectId); $comment->setMessage($data['message']); $comment->setVerb($data['verb']); $this->commentsManager->save($comment); return $comment; } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid input values', 0, $e); } catch (\OCP\Comments\MessageTooLongException $e) { $msg = 'Message exceeds allowed character limit of '; throw new BadRequest($msg . \OCP\Comments\IComment::MAX_MESSAGE_LENGTH, 0, $e); } } } lib/Connector/Sabre/LockPlugin.php 0000604 00000004714 15247100403 0013056 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; class LockPlugin extends ServerPlugin { /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * {@inheritdoc} */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', [$this, 'getLock'], 50); $this->server->on('afterMethod', [$this, 'releaseLock'], 50); } public function getLock(RequestInterface $request) { // we can't listen on 'beforeMethod:PUT' due to order of operations with setting up the tree // so instead we limit ourselves to the PUT method manually if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { return; } try { $node = $this->server->tree->getNodeForPath($request->getPath()); } catch (NotFound $e) { return; } if ($node instanceof Node) { try { $node->acquireLock(ILockingProvider::LOCK_SHARED); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } } public function releaseLock(RequestInterface $request) { if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { return; } try { $node = $this->server->tree->getNodeForPath($request->getPath()); } catch (NotFound $e) { return; } if ($node instanceof Node) { $node->releaseLock(ILockingProvider::LOCK_SHARED); } } } lib/Connector/Sabre/Auth.php 0000604 00000017520 15247100403 0011707 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@owncloud.com> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Markus Goetz <markus@woboq.com> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Exception; use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\Authentication\TwoFactorAuth\Manager; use OC\Security\Bruteforce\Throttler; use OC\User\Session; use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden; use OCP\IRequest; use OCP\ISession; use Sabre\DAV\Auth\Backend\AbstractBasic; use Sabre\DAV\Exception\NotAuthenticated; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class Auth extends AbstractBasic { const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND'; /** @var ISession */ private $session; /** @var Session */ private $userSession; /** @var IRequest */ private $request; /** @var string */ private $currentUser; /** @var Manager */ private $twoFactorManager; /** @var Throttler */ private $throttler; /** * @param ISession $session * @param Session $userSession * @param IRequest $request * @param Manager $twoFactorManager * @param Throttler $throttler * @param string $principalPrefix */ public function __construct(ISession $session, Session $userSession, IRequest $request, Manager $twoFactorManager, Throttler $throttler, $principalPrefix = 'principals/users/') { $this->session = $session; $this->userSession = $userSession; $this->twoFactorManager = $twoFactorManager; $this->request = $request; $this->throttler = $throttler; $this->principalPrefix = $principalPrefix; // setup realm $defaults = new \OCP\Defaults(); $this->realm = $defaults->getName(); } /** * Whether the user has initially authenticated via DAV * * This is required for WebDAV clients that resent the cookies even when the * account was changed. * * @see https://github.com/owncloud/core/issues/13245 * * @param string $username * @return bool */ public function isDavAuthenticated($username) { return !is_null($this->session->get(self::DAV_AUTHENTICATED)) && $this->session->get(self::DAV_AUTHENTICATED) === $username; } /** * Validates a username and password * * This method should return true or false depending on if login * succeeded. * * @param string $username * @param string $password * @return bool * @throws PasswordLoginForbidden */ protected function validateUserPass($username, $password) { if ($this->userSession->isLoggedIn() && $this->isDavAuthenticated($this->userSession->getUser()->getUID()) ) { \OC_Util::setupFS($this->userSession->getUser()->getUID()); $this->session->close(); return true; } else { \OC_Util::setupFS(); //login hooks may need early access to the filesystem try { if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) { \OC_Util::setupFS($this->userSession->getUser()->getUID()); $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); $this->session->close(); return true; } else { $this->session->close(); return false; } } catch (PasswordLoginForbiddenException $ex) { $this->session->close(); throw new PasswordLoginForbidden(); } } } /** * @param RequestInterface $request * @param ResponseInterface $response * @return array * @throws NotAuthenticated * @throws ServiceUnavailable */ function check(RequestInterface $request, ResponseInterface $response) { try { $result = $this->auth($request, $response); return $result; } catch (NotAuthenticated $e) { throw $e; } catch (Exception $e) { $class = get_class($e); $msg = $e->getMessage(); \OC::$server->getLogger()->logException($e); throw new ServiceUnavailable("$class: $msg"); } } /** * Checks whether a CSRF check is required on the request * * @return bool */ private function requiresCSRFCheck() { // GET requires no check at all if($this->request->getMethod() === 'GET') { return false; } // Official Nextcloud clients require no checks if($this->request->isUserAgent([ IRequest::USER_AGENT_CLIENT_DESKTOP, IRequest::USER_AGENT_CLIENT_ANDROID, IRequest::USER_AGENT_CLIENT_IOS, ])) { return false; } // If not logged-in no check is required if(!$this->userSession->isLoggedIn()) { return false; } // POST always requires a check if($this->request->getMethod() === 'POST') { return true; } // If logged-in AND DAV authenticated no check is required if($this->userSession->isLoggedIn() && $this->isDavAuthenticated($this->userSession->getUser()->getUID())) { return false; } return true; } /** * @param RequestInterface $request * @param ResponseInterface $response * @return array * @throws NotAuthenticated */ private function auth(RequestInterface $request, ResponseInterface $response) { $forcedLogout = false; if(!$this->request->passesCSRFCheck() && $this->requiresCSRFCheck()) { // In case of a fail with POST we need to recheck the credentials if($this->request->getMethod() === 'POST') { $forcedLogout = true; } else { $response->setStatus(401); throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.'); } } if($forcedLogout) { $this->userSession->logout(); } else { if($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) { throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.'); } if (\OC_User::handleApacheAuth() || //Fix for broken webdav clients ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) || //Well behaved clients that only send the cookie are allowed ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) ) { $user = $this->userSession->getUser()->getUID(); \OC_Util::setupFS($user); $this->currentUser = $user; $this->session->close(); return [true, $this->principalPrefix . $user]; } } if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With')))) { // do not re-authenticate over ajax, use dummy auth name to prevent browser popup $response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"'); $response->setStatus(401); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } $data = parent::check($request, $response); if($data[0] === true) { $startPos = strrpos($data[1], '/') + 1; $user = $this->userSession->getUser()->getUID(); $data[1] = substr_replace($data[1], $user, $startPos); } return $data; } } lib/Connector/Sabre/Principal.php 0000604 00000013646 15247100403 0012734 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> * @author Jakob Sack <mail@jakobsack.de> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; use Sabre\DAV\Exception; use \Sabre\DAV\PropPatch; use Sabre\DAVACL\PrincipalBackend\BackendInterface; use Sabre\HTTP\URLUtil; class Principal implements BackendInterface { /** @var IUserManager */ private $userManager; /** @var IGroupManager */ private $groupManager; /** @var string */ private $principalPrefix; /** @var bool */ private $hasGroups; /** * @param IUserManager $userManager * @param IGroupManager $groupManager * @param string $principalPrefix */ public function __construct(IUserManager $userManager, IGroupManager $groupManager, $principalPrefix = 'principals/users/') { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->principalPrefix = trim($principalPrefix, '/'); $this->hasGroups = ($principalPrefix === 'principals/users/'); } /** * Returns a list of principals based on a prefix. * * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * * You are expected to return at least a 'uri' for every user, you can * return any additional properties if you wish so. Common properties are: * {DAV:}displayname * * @param string $prefixPath * @return string[] */ public function getPrincipalsByPrefix($prefixPath) { $principals = []; if ($prefixPath === $this->principalPrefix) { foreach($this->userManager->search('') as $user) { $principals[] = $this->userToPrincipal($user); } } return $principals; } /** * Returns a specific principal, specified by it's path. * The returned structure should be the exact same as from * getPrincipalsByPrefix. * * @param string $path * @return array */ public function getPrincipalByPath($path) { list($prefix, $name) = URLUtil::splitPath($path); if ($prefix === $this->principalPrefix) { $user = $this->userManager->get($name); if (!is_null($user)) { return $this->userToPrincipal($user); } } return null; } /** * Returns the list of members for a group-principal * * @param string $principal * @return string[] * @throws Exception */ public function getGroupMemberSet($principal) { // TODO: for now the group principal has only one member, the user itself $principal = $this->getPrincipalByPath($principal); if (!$principal) { throw new Exception('Principal not found'); } return [$principal['uri']]; } /** * Returns the list of groups a principal is a member of * * @param string $principal * @param bool $needGroups * @return array * @throws Exception */ public function getGroupMembership($principal, $needGroups = false) { list($prefix, $name) = URLUtil::splitPath($principal); if ($prefix === $this->principalPrefix) { $user = $this->userManager->get($name); if (!$user) { throw new Exception('Principal not found'); } if ($this->hasGroups || $needGroups) { $groups = $this->groupManager->getUserGroups($user); $groups = array_map(function($group) { /** @var IGroup $group */ return 'principals/groups/' . urlencode($group->getGID()); }, $groups); return $groups; } } return []; } /** * Updates the list of group members for a group principal. * * The principals should be passed as a list of uri's. * * @param string $principal * @param string[] $members * @throws Exception */ public function setGroupMemberSet($principal, array $members) { throw new Exception('Setting members of the group is not supported yet'); } /** * @param string $path * @param PropPatch $propPatch * @return int */ function updatePrincipal($path, PropPatch $propPatch) { return 0; } /** * @param string $prefixPath * @param array $searchProperties * @param string $test * @return array */ function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { return []; } /** * @param string $uri * @param string $principalPrefix * @return string */ function findByUri($uri, $principalPrefix) { if (substr($uri, 0, 7) === 'mailto:') { $email = substr($uri, 7); $users = $this->userManager->getByEmail($email); if (count($users) === 1) { return $this->principalPrefix . '/' . $users[0]->getUID(); } } return ''; } /** * @param IUser $user * @return array */ protected function userToPrincipal($user) { $userId = $user->getUID(); $displayName = $user->getDisplayName(); $principal = [ 'uri' => $this->principalPrefix . '/' . $userId, '{DAV:}displayname' => is_null($displayName) ? $userId : $displayName, ]; $email = $user->getEMailAddress(); if (!empty($email)) { $principal['{http://sabredav.org/ns}email-address'] = $email; } return $principal; } public function getPrincipalPrefix() { return $this->principalPrefix; } } lib/Connector/Sabre/FilesPlugin.php 0000604 00000036727 15247100403 0013241 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; use OCP\Files\ForbiddenException; use OCP\IPreview; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\IFile; use \Sabre\DAV\PropFind; use \Sabre\DAV\PropPatch; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Tree; use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IRequest; use OCA\DAV\Upload\FutureFile; class FilesPlugin extends ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const NS_NEXTCLOUD = 'http://nextcloud.org/ns'; const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id'; const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid'; const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions'; const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions'; const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL'; const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size'; const GETETAG_PROPERTYNAME = '{DAV:}getetag'; const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified'; const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id'; const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name'; const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums'; const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint'; const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview'; const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type'; const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var Tree */ private $tree; /** * Whether this is public webdav. * If true, some returned information will be stripped off. * * @var bool */ private $isPublic; /** * @var View */ private $fileView; /** * @var bool */ private $downloadAttachment; /** * @var IConfig */ private $config; /** * @var IRequest */ private $request; /** * @var IPreview */ private $previewManager; /** * @param Tree $tree * @param IConfig $config * @param IRequest $request * @param IPreview $previewManager * @param bool $isPublic * @param bool $downloadAttachment */ public function __construct(Tree $tree, IConfig $config, IRequest $request, IPreview $previewManager, $isPublic = false, $downloadAttachment = true) { $this->tree = $tree; $this->config = $config; $this->request = $request; $this->isPublic = $isPublic; $this->downloadAttachment = $downloadAttachment; $this->previewManager = $previewManager; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc'; $server->protectedProperties[] = self::FILEID_PROPERTYNAME; $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME; $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME; $server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME; $server->protectedProperties[] = self::SIZE_PROPERTYNAME; $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME; $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME; $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME; $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME; $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME; $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME; $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME; $server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME; // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH $allowedProperties = ['{DAV:}getetag']; $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties); $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); $this->server->on('propPatch', array($this, 'handleUpdateProperties')); $this->server->on('afterBind', array($this, 'sendFileIdHeader')); $this->server->on('afterWriteContent', array($this, 'sendFileIdHeader')); $this->server->on('afterMethod:GET', [$this,'httpGet']); $this->server->on('afterMethod:GET', array($this, 'handleDownloadToken')); $this->server->on('afterResponse', function($request, ResponseInterface $response) { $body = $response->getBody(); if (is_resource($body)) { fclose($body); } }); $this->server->on('beforeMove', [$this, 'checkMove']); $this->server->on('beforeMove', [$this, 'beforeMoveFutureFile']); } /** * Plugin that checks if a move can actually be performed. * * @param string $source source path * @param string $destination destination path * @throws Forbidden * @throws NotFound */ function checkMove($source, $destination) { $sourceNode = $this->tree->getNodeForPath($source); if (!$sourceNode instanceof Node) { return; } list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($source); list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); if ($sourceDir !== $destinationDir) { $sourceNodeFileInfo = $sourceNode->getFileInfo(); if (is_null($sourceNodeFileInfo)) { throw new NotFound($source . ' does not exist'); } if (!$sourceNodeFileInfo->isDeletable()) { throw new Forbidden($source . " cannot be deleted"); } } } /** * This sets a cookie to be able to recognize the start of the download * the content must not be longer than 32 characters and must only contain * alphanumeric characters * * @param RequestInterface $request * @param ResponseInterface $response */ function handleDownloadToken(RequestInterface $request, ResponseInterface $response) { $queryParams = $request->getQueryParameters(); /** * this sets a cookie to be able to recognize the start of the download * the content must not be longer than 32 characters and must only contain * alphanumeric characters */ if (isset($queryParams['downloadStartSecret'])) { $token = $queryParams['downloadStartSecret']; if (!isset($token[32]) && preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) { // FIXME: use $response->setHeader() instead setcookie('ocDownloadStarted', $token, time() + 20, '/'); } } } /** * Add headers to file download * * @param RequestInterface $request * @param ResponseInterface $response */ function httpGet(RequestInterface $request, ResponseInterface $response) { // Only handle valid files $node = $this->tree->getNodeForPath($request->getPath()); if (!($node instanceof IFile)) return; // adds a 'Content-Disposition: attachment' header in case no disposition // header has been set before if ($this->downloadAttachment && $response->getHeader('Content-Disposition') === null) { $filename = $node->getName(); if ($this->request->isUserAgent( [ \OC\AppFramework\Http\Request::USER_AGENT_IE, \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, ])) { $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"'); } else { $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename) . '; filename="' . rawurlencode($filename) . '"'); } } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { //Add OC-Checksum header /** @var $node File */ $checksum = $node->getChecksum(); if ($checksum !== null && $checksum !== '') { $response->addHeader('OC-Checksum', $checksum); } } } /** * Adds all ownCloud-specific properties * * @param PropFind $propFind * @param \Sabre\DAV\INode $node * @return void */ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) { $httpRequest = $this->server->httpRequest; if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { /** * This was disabled, because it made dir listing throw an exception, * so users were unable to navigate into folders where one subitem * is blocked by the files_accesscontrol app, see: * https://github.com/nextcloud/files_accesscontrol/issues/65 if (!$node->getFileInfo()->isReadable()) { // avoid detecting files through this means throw new NotFound(); } */ $propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) { return $node->getFileId(); }); $propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) { return $node->getInternalFileId(); }); $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) { $perms = $node->getDavPermissions(); if ($this->isPublic) { // remove mount information $perms = str_replace(['S', 'M'], '', $perms); } return $perms; }); $propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) { return $node->getSharePermissions( $httpRequest->getRawServerValue('PHP_AUTH_USER') ); }); $propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) { return $node->getETag(); }); $propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) { $owner = $node->getOwner(); if (!$owner) { return null; } else { return $owner->getUID(); } }); $propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) { $owner = $node->getOwner(); if (!$owner) { return null; } else { return $owner->getDisplayName(); } }); $propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function() use ($node) { $result = $node->getFileInfo()->isEncrypted() ? '1' : '0'; return $result; }); $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) { return json_encode($this->previewManager->isAvailable($node->getFileInfo())); }); $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) { return $node->getSize(); }); $propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) { return $node->getFileInfo()->getMountPoint()->getMountType(); }); } if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) { return $this->config->getSystemValue('data-fingerprint', ''); }); } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) { /** @var $node \OCA\DAV\Connector\Sabre\File */ try { $directDownloadUrl = $node->getDirectDownload(); if (isset($directDownloadUrl['url'])) { return $directDownloadUrl['url']; } } catch (StorageNotAvailableException $e) { return false; } catch (ForbiddenException $e) { return false; } return false; }); $propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) { $checksum = $node->getChecksum(); if ($checksum === NULL || $checksum === '') { return null; } return new ChecksumList($checksum); }); } if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) { $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) { return $node->getSize(); }); } } /** * Update ownCloud-specific properties * * @param string $path * @param PropPatch $propPatch * * @return void */ public function handleUpdateProperties($path, PropPatch $propPatch) { $node = $this->tree->getNodeForPath($path); if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } $propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($node) { if (empty($time)) { return false; } $node->touch($time); return true; }); $propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($node) { if (empty($etag)) { return false; } if ($node->setEtag($etag) !== -1) { return true; } return false; }); } /** * @param string $filePath * @param \Sabre\DAV\INode $node * @throws \Sabre\DAV\Exception\BadRequest */ public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) { // chunked upload handling if (isset($_SERVER['HTTP_OC_CHUNKED'])) { list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath); $info = \OC_FileChunking::decodeName($name); if (!empty($info)) { $filePath = $path . '/' . $info['name']; } } // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder if (!$this->server->tree->nodeExists($filePath)) { return; } $node = $this->server->tree->getNodeForPath($filePath); if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { $fileId = $node->getFileId(); if (!is_null($fileId)) { $this->server->httpResponse->setHeader('OC-FileId', $fileId); } } } /** * Move handler for future file. * * This overrides the default move behavior to prevent Sabre * to delete the target file before moving. Because deleting would * lose the file id and metadata. * * @param string $path source path * @param string $destination destination path * @return bool|void false to stop handling, void to skip this handler */ public function beforeMoveFutureFile($path, $destination) { $sourceNode = $this->tree->getNodeForPath($path); if (!$sourceNode instanceof FutureFile) { // skip handling as the source is not a chunked FutureFile return; } if (!$this->tree->nodeExists($destination)) { // skip and let the default handler do its work return; } // do a move manually, skipping Sabre's default "delete" for existing nodes $this->tree->move($path, $destination); // trigger all default events (copied from CorePlugin::move) $this->server->emit('afterMove', [$path, $destination]); $this->server->emit('afterUnbind', [$path]); $this->server->emit('afterBind', [$destination]); $response = $this->server->httpResponse; $response->setHeader('Content-Length', '0'); $response->setStatus(204); return false; } } lib/Connector/Sabre/Server.php 0000604 00000002457 15247100403 0012257 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author scolebrook <scolebrook@mac.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; /** * Class \OCA\DAV\Connector\Sabre\Server * * This class overrides some methods from @see \Sabre\DAV\Server. * * @see \Sabre\DAV\Server */ class Server extends \Sabre\DAV\Server { /** * @see \Sabre\DAV\Server */ public function __construct($treeOrNode = null) { parent::__construct($treeOrNode); self::$exposeVersion = false; $this->enablePropfindDepthInfinity = true; } } lib/Connector/Sabre/ChecksumList.php 0000604 00000003754 15247100403 0013410 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\Xml\XmlSerializable; use Sabre\Xml\Writer; /** * Checksumlist property * * This property contains multiple "checksum" elements, each containing a * checksum name. */ class ChecksumList implements XmlSerializable { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** @var string[] of TYPE:CHECKSUM */ private $checksums; /** * @param string $checksum */ public function __construct($checksum) { $this->checksums = explode(',', $checksum); } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { foreach ($this->checksums as $checksum) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}checksum', $checksum); } } } lib/Connector/Sabre/TagsPlugin.php 0000604 00000016662 15247100403 0013071 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; /** * ownCloud * * @author Vincent Petry * @copyright 2014 Vincent Petry <pvince81@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/>. * */ use \Sabre\DAV\PropFind; use \Sabre\DAV\PropPatch; class TagsPlugin extends \Sabre\DAV\ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags'; const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite'; const TAG_FAVORITE = '_$!<Favorite>!$_'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var \OCP\ITagManager */ private $tagManager; /** * @var \OCP\ITags */ private $tagger; /** * Array of file id to tags array * The null value means the cache wasn't initialized. * * @var array */ private $cachedTags; /** * @var \Sabre\DAV\Tree */ private $tree; /** * @param \Sabre\DAV\Tree $tree tree * @param \OCP\ITagManager $tagManager tag manager */ public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) { $this->tree = $tree; $this->tagManager = $tagManager; $this->tagger = null; $this->cachedTags = array(); } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $server->xml->elementMap[self::TAGS_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\TagList'; $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); $this->server->on('propPatch', array($this, 'handleUpdateProperties')); } /** * Returns the tagger * * @return \OCP\ITags tagger */ private function getTagger() { if (!$this->tagger) { $this->tagger = $this->tagManager->load('files'); } return $this->tagger; } /** * Returns tags and favorites. * * @param integer $fileId file id * @return array list($tags, $favorite) with $tags as tag array * and $favorite is a boolean whether the file was favorited */ private function getTagsAndFav($fileId) { $isFav = false; $tags = $this->getTags($fileId); if ($tags) { $favPos = array_search(self::TAG_FAVORITE, $tags); if ($favPos !== false) { $isFav = true; unset($tags[$favPos]); } } return array($tags, $isFav); } /** * Returns tags for the given file id * * @param integer $fileId file id * @return array list of tags for that file */ private function getTags($fileId) { if (isset($this->cachedTags[$fileId])) { return $this->cachedTags[$fileId]; } else { $tags = $this->getTagger()->getTagsForObjects(array($fileId)); if ($tags !== false) { if (empty($tags)) { return array(); } return current($tags); } } return null; } /** * Updates the tags of the given file id * * @param int $fileId * @param array $tags array of tag strings */ private function updateTags($fileId, $tags) { $tagger = $this->getTagger(); $currentTags = $this->getTags($fileId); $newTags = array_diff($tags, $currentTags); foreach ($newTags as $tag) { if ($tag === self::TAG_FAVORITE) { continue; } $tagger->tagAs($fileId, $tag); } $deletedTags = array_diff($currentTags, $tags); foreach ($deletedTags as $tag) { if ($tag === self::TAG_FAVORITE) { continue; } $tagger->unTag($fileId, $tag); } } /** * Adds tags and favorites properties to the response, * if requested. * * @param PropFind $propFind * @param \Sabre\DAV\INode $node * @return void */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $node ) { if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } // need prefetch ? if ($node instanceof \OCA\DAV\Connector\Sabre\Directory && $propFind->getDepth() !== 0 && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME)) || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME)) )) { // note: pre-fetching only supported for depth <= 1 $folderContent = $node->getChildren(); $fileIds[] = (int)$node->getId(); foreach ($folderContent as $info) { $fileIds[] = (int)$info->getId(); } $tags = $this->getTagger()->getTagsForObjects($fileIds); if ($tags === false) { // the tags API returns false on error... $tags = array(); } $this->cachedTags = $this->cachedTags + $tags; $emptyFileIds = array_diff($fileIds, array_keys($tags)); // also cache the ones that were not found foreach ($emptyFileIds as $fileId) { $this->cachedTags[$fileId] = []; } } $tags = null; $isFav = null; $propFind->handle(self::TAGS_PROPERTYNAME, function() use ($tags, &$isFav, $node) { list($tags, $isFav) = $this->getTagsAndFav($node->getId()); return new TagList($tags); }); $propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) { if (is_null($isFav)) { list(, $isFav) = $this->getTagsAndFav($node->getId()); } if ($isFav) { return 1; } else { return 0; } }); } /** * Updates tags and favorites properties, if applicable. * * @param string $path * @param PropPatch $propPatch * * @return void */ public function handleUpdateProperties($path, PropPatch $propPatch) { $node = $this->tree->getNodeForPath($path); if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } $propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($node) { $this->updateTags($node->getId(), $tagList->getTags()); return true; }); $propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($node) { if ((int)$favState === 1 || $favState === 'true') { $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE); } else { $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE); } if (is_null($favState)) { // confirm deletion return 204; } return 200; }); } } lib/Connector/Sabre/QuotaPlugin.php 0000604 00000010427 15247100403 0013255 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Felix Moeller <mail@felixmoeller.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author scambra <sergio@entrecables.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\Files\FileInfo; use OCP\Files\StorageNotAvailableException; use Sabre\DAV\Exception\InsufficientStorage; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\HTTP\URLUtil; /** * This plugin check user quota and deny creating files when they exceeds the quota. * * @author Sergio Cambra * @copyright Copyright (C) 2012 entreCables S.L. All rights reserved. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class QuotaPlugin extends \Sabre\DAV\ServerPlugin { /** * @var \OC\Files\View */ private $view; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @param \OC\Files\View $view */ public function __construct($view) { $this->view = $view; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the requires event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $server->on('beforeWriteContent', array($this, 'checkQuota'), 10); $server->on('beforeCreateFile', array($this, 'checkQuota'), 10); } /** * This method is called before any HTTP method and validates there is enough free space to store the file * * @param string $uri * @throws InsufficientStorage * @return bool */ public function checkQuota($uri) { $length = $this->getLength(); if ($length) { if (substr($uri, 0, 1) !== '/') { $uri = '/' . $uri; } list($parentUri, $newName) = URLUtil::splitPath($uri); if(is_null($parentUri)) { $parentUri = ''; } $req = $this->server->httpRequest; if ($req->getHeader('OC-Chunked')) { $info = \OC_FileChunking::decodeName($newName); $chunkHandler = $this->getFileChunking($info); // subtract the already uploaded size to see whether // there is still enough space for the remaining chunks $length -= $chunkHandler->getCurrentSize(); // use target file name for free space check in case of shared files $uri = rtrim($parentUri, '/') . '/' . $info['name']; } $freeSpace = $this->getFreeSpace($uri); if ($freeSpace !== FileInfo::SPACE_UNKNOWN && $freeSpace !== FileInfo::SPACE_UNLIMITED && $length > $freeSpace) { if (isset($chunkHandler)) { $chunkHandler->cleanup(); } throw new InsufficientStorage(); } } return true; } public function getFileChunking($info) { // FIXME: need a factory for better mocking support return new \OC_FileChunking($info); } public function getLength() { $req = $this->server->httpRequest; $length = $req->getHeader('X-Expected-Entity-Length'); if (!is_numeric($length)) { $length = $req->getHeader('Content-Length'); $length = is_numeric($length) ? $length : null; } $ocLength = $req->getHeader('OC-Total-Length'); if (is_numeric($length) && is_numeric($ocLength)) { return max($length, $ocLength); } return $length; } /** * @param string $uri * @return mixed * @throws ServiceUnavailable */ public function getFreeSpace($uri) { try { $freeSpace = $this->view->free_space(ltrim($uri, '/')); return $freeSpace; } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable($e->getMessage()); } } } lib/Connector/Sabre/ExceptionLoggerPlugin.php 0000604 00000006054 15247100403 0015263 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Pierre Jochem <pierrejochem@msn.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\ILogger; use Sabre\DAV\Exception; use Sabre\HTTP\Response; class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin { protected $nonFatalExceptions = [ 'Sabre\DAV\Exception\NotAuthenticated' => true, // If tokenauth can throw this exception (which is basically as // NotAuthenticated. So not fatal. 'OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden' => true, // the sync client uses this to find out whether files exist, // so it is not always an error, log it as debug 'Sabre\DAV\Exception\NotFound' => true, // this one mostly happens when the same file is uploaded at // exactly the same time from two clients, only one client // wins, the second one gets "Precondition failed" 'Sabre\DAV\Exception\PreconditionFailed' => true, // forbidden can be expected when trying to upload to // read-only folders for example 'Sabre\DAV\Exception\Forbidden' => true, // Happens when an external storage or federated share is temporarily // not available 'Sabre\DAV\Exception\StorageNotAvailableException' => true, ]; /** @var string */ private $appName; /** @var ILogger */ private $logger; /** * @param string $loggerAppName app name to use when logging * @param ILogger $logger */ public function __construct($loggerAppName, $logger) { $this->appName = $loggerAppName; $this->logger = $logger; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->on('exception', array($this, 'logException'), 10); } /** * Log exception * */ public function logException(\Exception $ex) { $exceptionClass = get_class($ex); $level = \OCP\Util::FATAL; if (isset($this->nonFatalExceptions[$exceptionClass])) { $level = \OCP\Util::DEBUG; } $this->logger->logException($ex, [ 'app' => $this->appName, 'level' => $level, ]); } } lib/Connector/Sabre/ShareTypeList.php 0000604 00000004123 15247100403 0013541 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\Xml\Element; use Sabre\Xml\Reader; use Sabre\Xml\Writer; /** * ShareTypeList property * * This property contains multiple "share-type" elements, each containing a share type. */ class ShareTypeList implements Element { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * Share types * * @var int[] */ private $shareTypes; /** * @param int[] $shareTypes */ public function __construct($shareTypes) { $this->shareTypes = $shareTypes; } /** * Returns the share types * * @return int[] */ public function getShareTypes() { return $this->shareTypes; } /** * The deserialize method is called during xml parsing. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $shareTypes = []; $tree = $reader->parseInnerTree(); if ($tree === null) { return null; } foreach ($tree as $elem) { if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}share-type') { $shareTypes[] = (int)$elem['value']; } } return new self($shareTypes); } /** * The xmlSerialize metod is called during xml writing. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { foreach ($this->shareTypes as $shareType) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}share-type', $shareType); } } } lib/Connector/Sabre/BearerAuth.php 0000604 00000005151 15247100403 0013025 0 ustar 00 <?php /** * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Connector\Sabre; use OCP\IRequest; use OCP\ISession; use OCP\IUserSession; use Sabre\DAV\Auth\Backend\AbstractBearer; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class BearerAuth extends AbstractBearer { /** @var IUserSession */ private $userSession; /** @var ISession */ private $session; /** @var IRequest */ private $request; /** @var string */ private $principalPrefix; /** * @param IUserSession $userSession * @param ISession $session * @param string $principalPrefix * @param IRequest $request */ public function __construct(IUserSession $userSession, ISession $session, IRequest $request, $principalPrefix = 'principals/users/') { $this->userSession = $userSession; $this->session = $session; $this->request = $request; $this->principalPrefix = $principalPrefix; // setup realm $defaults = new \OCP\Defaults(); $this->realm = $defaults->getName(); } private function setupUserFs($userId) { \OC_Util::setupFS($userId); $this->session->close(); return $this->principalPrefix . $userId; } /** * {@inheritdoc} */ public function validateBearerToken($bearerToken) { \OC_Util::setupFS(); if(!$this->userSession->isLoggedIn()) { $this->userSession->tryTokenLogin($this->request); } if($this->userSession->isLoggedIn()) { return $this->setupUserFs($this->userSession->getUser()->getUID()); } return false; } /** * \Sabre\DAV\Auth\Backend\AbstractBearer::challenge sets an WWW-Authenticate * header which some DAV clients can't handle. Thus we override this function * and make it simply return a 401. * * @param RequestInterface $request * @param ResponseInterface $response */ public function challenge(RequestInterface $request, ResponseInterface $response) { $response->setStatus(401); } } lib/Connector/Sabre/TagList.php 0000604 00000006162 15247100403 0012355 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\Xml\Element; use Sabre\Xml\Reader; use Sabre\Xml\Writer; /** * TagList property * * This property contains multiple "tag" elements, each containing a tag name. */ class TagList implements Element { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * tags * * @var array */ private $tags; /** * @param array $tags */ public function __construct(array $tags) { $this->tags = $tags; } /** * Returns the tags * * @return array */ public function getTags() { return $this->tags; } /** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $tags = []; $tree = $reader->parseInnerTree(); if ($tree === null) { return null; } foreach ($tree as $elem) { if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') { $tags[] = $elem['value']; } } return new self($tags); } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { foreach ($this->tags as $tag) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}tag', $tag); } } } lib/Connector/Sabre/CommentPropertiesPlugin.php 0000604 00000011115 15247100403 0015636 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\Comments\ICommentsManager; use OCP\IUserSession; use Sabre\DAV\PropFind; use Sabre\DAV\ServerPlugin; class CommentPropertiesPlugin extends ServerPlugin { const PROPERTY_NAME_HREF = '{http://owncloud.org/ns}comments-href'; const PROPERTY_NAME_COUNT = '{http://owncloud.org/ns}comments-count'; const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread'; /** @var \Sabre\DAV\Server */ protected $server; /** @var ICommentsManager */ private $commentsManager; /** @var IUserSession */ private $userSession; private $cachedUnreadCount = []; private $cachedFolders = []; public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) { $this->commentsManager = $commentsManager; $this->userSession = $userSession; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); } /** * Adds tags and favorites properties to the response, * if requested. * * @param PropFind $propFind * @param \Sabre\DAV\INode $node * @return void */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $node ) { if (!($node instanceof File) && !($node instanceof Directory)) { return; } // need prefetch ? if ($node instanceof \OCA\DAV\Connector\Sabre\Directory && $propFind->getDepth() !== 0 && !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD)) ) { $unreadCounts = $this->commentsManager->getNumberOfUnreadCommentsForFolder($node->getId(), $this->userSession->getUser()); $this->cachedFolders[] = $node->getPath(); foreach ($unreadCounts as $id => $count) { $this->cachedUnreadCount[$id] = $count; } } $propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) { return $this->commentsManager->getNumberOfCommentsForObject('files', strval($node->getId())); }); $propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) { return $this->getCommentsLink($node); }); $propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) { if (isset($this->cachedUnreadCount[$node->getId()])) { return $this->cachedUnreadCount[$node->getId()]; } else { list($parentPath,) = \Sabre\Uri\split($node->getPath()); if ($parentPath === '') { $parentPath = '/'; } // if we already cached the folder this file is in we know there are no shares for this file if (array_search($parentPath, $this->cachedFolders) === false) { return $this->getUnreadCount($node); } else { return 0; } } }); } /** * returns a reference to the comments node * * @param Node $node * @return mixed|string */ public function getCommentsLink(Node $node) { $href = $this->server->getBaseUri(); $entryPoint = strpos($href, '/remote.php/'); if($entryPoint === false) { // in case we end up somewhere else, unexpectedly. return null; } $commentsPart = 'dav/comments/files/' . rawurldecode($node->getId()); $href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/')); return $href; } /** * returns the number of unread comments for the currently logged in user * on the given file or directory node * * @param Node $node * @return Int|null */ public function getUnreadCount(Node $node) { $user = $this->userSession->getUser(); if(is_null($user)) { return null; } $lastRead = $this->commentsManager->getReadMark('files', strval($node->getId()), $user); return $this->commentsManager->getNumberOfCommentsForObject('files', strval($node->getId()), $lastRead); } } lib/Connector/Sabre/ServerFactory.php 0000604 00000015043 15247100403 0013602 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\Node\Folder; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCP\Files\Mount\IMountManager; use OCP\IConfig; use OCP\IDBConnection; use OCP\ILogger; use OCP\IPreview; use OCP\IRequest; use OCP\ITagManager; use OCP\IUserSession; use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\DAV\Auth\Plugin; class ServerFactory { /** @var IConfig */ private $config; /** @var ILogger */ private $logger; /** @var IDBConnection */ private $databaseConnection; /** @var IUserSession */ private $userSession; /** @var IMountManager */ private $mountManager; /** @var ITagManager */ private $tagManager; /** @var IRequest */ private $request; /** @var IPreview */ private $previewManager; /** * @param IConfig $config * @param ILogger $logger * @param IDBConnection $databaseConnection * @param IUserSession $userSession * @param IMountManager $mountManager * @param ITagManager $tagManager * @param IRequest $request * @param IPreview $previewManager */ public function __construct( IConfig $config, ILogger $logger, IDBConnection $databaseConnection, IUserSession $userSession, IMountManager $mountManager, ITagManager $tagManager, IRequest $request, IPreview $previewManager ) { $this->config = $config; $this->logger = $logger; $this->databaseConnection = $databaseConnection; $this->userSession = $userSession; $this->mountManager = $mountManager; $this->tagManager = $tagManager; $this->request = $request; $this->previewManager = $previewManager; } /** * @param string $baseUri * @param string $requestUri * @param Plugin $authPlugin * @param callable $viewCallBack callback that should return the view for the dav endpoint * @return Server */ public function createServer($baseUri, $requestUri, Plugin $authPlugin, callable $viewCallBack) { // Fire up server $objectTree = new \OCA\DAV\Connector\Sabre\ObjectTree(); $server = new \OCA\DAV\Connector\Sabre\Server($objectTree); // Set URL explicitly due to reverse-proxy situations $server->httpRequest->setUrl($requestUri); $server->setBaseUri($baseUri); // Load plugins $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); $server->addPlugin($authPlugin); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin()); // Some WebDAV clients do require Class 2 WebDAV support (locking), since // we do not provide locking we emulate it using a fake locking plugin. if($this->request->isUserAgent([ '/WebDAVFS/', '/Microsoft Office OneNote 2013/', '/Microsoft-WebDAV-MiniRedir/', ])) { $server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin()); } if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) { $server->addPlugin(new BrowserErrorPagePlugin()); } // wait with registering these until auth is handled and the filesystem is setup $server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) { // ensure the skeleton is copied $userFolder = \OC::$server->getUserFolder(); /** @var \OC\Files\View $view */ $view = $viewCallBack($server); if ($userFolder instanceof Folder && $userFolder->getPath() === $view->getRoot()) { $rootInfo = $userFolder; } else { $rootInfo = $view->getFileInfo(''); } // Create Nextcloud Dir if ($rootInfo->getType() === 'dir') { $root = new \OCA\DAV\Connector\Sabre\Directory($view, $rootInfo, $objectTree); } else { $root = new \OCA\DAV\Connector\Sabre\File($view, $rootInfo); } $objectTree->init($root, $view, $this->mountManager); $server->addPlugin( new \OCA\DAV\Connector\Sabre\FilesPlugin( $objectTree, $this->config, $this->request, $this->previewManager, false, !$this->config->getSystemValue('debug', false) ) ); $server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view)); if($this->userSession->isLoggedIn()) { $server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin( $objectTree, $this->userSession, $userFolder, \OC::$server->getShareManager() )); $server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesReportPlugin( $objectTree, $view, \OC::$server->getSystemTagManager(), \OC::$server->getSystemTagObjectMapper(), \OC::$server->getTagManager(), $this->userSession, \OC::$server->getGroupManager(), $userFolder )); // custom properties plugin must be the last one $server->addPlugin( new \Sabre\DAV\PropertyStorage\Plugin( new \OCA\DAV\Connector\Sabre\CustomPropertiesBackend( $objectTree, $this->databaseConnection, $this->userSession->getUser() ) ) ); } $server->addPlugin(new \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin()); }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request return $server; } } lib/Connector/Sabre/MaintenancePlugin.php 0000604 00000004515 15247100403 0014407 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IConfig; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\ServerPlugin; class MaintenancePlugin extends ServerPlugin { /** @var IConfig */ private $config; /** * Reference to main server object * * @var Server */ private $server; /** * @param IConfig $config */ public function __construct(IConfig $config = null) { $this->config = $config; if (is_null($config)) { $this->config = \OC::$server->getConfig(); } } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', array($this, 'checkMaintenanceMode'), 1); } /** * This method is called before any HTTP method and returns http status code 503 * in case the system is in maintenance mode. * * @throws ServiceUnavailable * @return bool */ public function checkMaintenanceMode() { if ($this->config->getSystemValue('maintenance', false)) { throw new ServiceUnavailable('System in maintenance mode.'); } if (\OC::checkUpgrade(false)) { throw new ServiceUnavailable('Upgrade needed'); } return true; } } lib/Connector/Sabre/SharesPlugin.php 0000604 00000012627 15247100403 0013415 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use \Sabre\DAV\PropFind; use OCP\IUserSession; use OCP\Share\IShare; /** * Sabre Plugin to provide share-related properties */ class SharesPlugin extends \Sabre\DAV\ServerPlugin { const NS_OWNCLOUD = 'http://owncloud.org/ns'; const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var \OCP\Share\IManager */ private $shareManager; /** * @var \Sabre\DAV\Tree */ private $tree; /** * @var string */ private $userId; /** * @var \OCP\Files\Folder */ private $userFolder; /** * @var IShare[] */ private $cachedShareTypes; private $cachedFolders = []; /** * @param \Sabre\DAV\Tree $tree tree * @param IUserSession $userSession user session * @param \OCP\Files\Folder $userFolder user home folder * @param \OCP\Share\IManager $shareManager share manager */ public function __construct( \Sabre\DAV\Tree $tree, IUserSession $userSession, \OCP\Files\Folder $userFolder, \OCP\Share\IManager $shareManager ) { $this->tree = $tree; $this->shareManager = $shareManager; $this->userFolder = $userFolder; $this->userId = $userSession->getUser()->getUID(); $this->cachedShareTypes = []; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc'; $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\ShareTypeList'; $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME; $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); } /** * Return a list of share types for outgoing shares * * @param \OCP\Files\Node $node file node * * @return int[] array of share types */ private function getShareTypes(\OCP\Files\Node $node) { $shareTypes = []; $requestedShareTypes = [ \OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE, \OCP\Share::SHARE_TYPE_EMAIL, ]; foreach ($requestedShareTypes as $requestedShareType) { // one of each type is enough to find out about the types $shares = $this->shareManager->getSharesBy( $this->userId, $requestedShareType, $node, false, 1 ); if (!empty($shares)) { $shareTypes[] = $requestedShareType; } } return $shareTypes; } private function getSharesTypesInFolder(\OCP\Files\Folder $node) { $shares = $this->shareManager->getSharesInFolder( $this->userId, $node, true ); $shareTypesByFileId = []; foreach($shares as $fileId => $sharesForFile) { $types = array_map(function(IShare $share) { return $share->getShareType(); }, $sharesForFile); $types = array_unique($types); sort($types); $shareTypesByFileId[$fileId] = $types; } return $shareTypesByFileId; } /** * Adds shares to propfind response * * @param PropFind $propFind propfind object * @param \Sabre\DAV\INode $sabreNode sabre node */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $sabreNode ) { if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } // need prefetch ? if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory && $propFind->getDepth() !== 0 && !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ) { $folderNode = $this->userFolder->get($sabreNode->getPath()); $childShares = $this->getSharesTypesInFolder($folderNode); $this->cachedFolders[] = $sabreNode->getPath(); $this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode); foreach ($childShares as $id => $shares) { $this->cachedShareTypes[$id] = $shares; } } $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) { if (isset($this->cachedShareTypes[$sabreNode->getId()])) { $shareTypes = $this->cachedShareTypes[$sabreNode->getId()]; } else { list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath()); if ($parentPath === '') { $parentPath = '/'; } // if we already cached the folder this file is in we know there are no shares for this file if (array_search($parentPath, $this->cachedFolders) === false) { $node = $this->userFolder->get($sabreNode->getPath()); $shareTypes = $this->getShareTypes($node); } else { return []; } } return new ShareTypeList($shareTypes); }); } } lib/Connector/Sabre/FilesReportPlugin.php 0000604 00000024267 15247100403 0014431 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Tree; use Sabre\DAV\Xml\Element\Response; use Sabre\DAV\Xml\Response\MultiStatus; use Sabre\DAV\PropFind; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\IUserSession; use OCP\Files\Folder; use OCP\IGroupManager; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\TagNotFoundException; use OCP\ITagManager; class FilesReportPlugin extends ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const REPORT_NAME = '{http://owncloud.org/ns}filter-files'; const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var Tree */ private $tree; /** * @var View */ private $fileView; /** * @var ISystemTagManager */ private $tagManager; /** * @var ISystemTagObjectMapper */ private $tagMapper; /** * Manager for private tags * * @var ITagManager */ private $fileTagger; /** * @var IUserSession */ private $userSession; /** * @var IGroupManager */ private $groupManager; /** * @var Folder */ private $userFolder; /** * @param Tree $tree * @param View $view * @param ISystemTagManager $tagManager * @param ISystemTagObjectMapper $tagMapper * @param ITagManager $fileTagger manager for private tags * @param IUserSession $userSession * @param IGroupManager $groupManager * @param Folder $userFolder */ public function __construct(Tree $tree, View $view, ISystemTagManager $tagManager, ISystemTagObjectMapper $tagMapper, ITagManager $fileTagger, IUserSession $userSession, IGroupManager $groupManager, Folder $userFolder ) { $this->tree = $tree; $this->fileView = $view; $this->tagManager = $tagManager; $this->tagMapper = $tagMapper; $this->fileTagger = $fileTagger; $this->userSession = $userSession; $this->groupManager = $groupManager; $this->userFolder = $userFolder; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $this->server = $server; $this->server->on('report', array($this, 'onReport')); } /** * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. * * @param string $uri * @return array */ public function getSupportedReportSet($uri) { return [self::REPORT_NAME]; } /** * REPORT operations to look for files * * @param string $reportName * @param $report * @param string $uri * @return bool * @throws BadRequest * @throws PreconditionFailed * @internal param $ [] $report */ public function onReport($reportName, $report, $uri) { $reportTargetNode = $this->server->tree->getNodeForPath($uri); if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) { return; } $ns = '{' . $this::NS_OWNCLOUD . '}'; $requestedProps = []; $filterRules = []; // parse report properties and gather filter info foreach ($report as $reportProps) { $name = $reportProps['name']; if ($name === $ns . 'filter-rules') { $filterRules = $reportProps['value']; } else if ($name === '{DAV:}prop') { // propfind properties foreach ($reportProps['value'] as $propVal) { $requestedProps[] = $propVal['name']; } } } if (empty($filterRules)) { // an empty filter would return all existing files which would be slow throw new BadRequest('Missing filter-rule block in request'); } // gather all file ids matching filter try { $resultFileIds = $this->processFilterRules($filterRules); } catch (TagNotFoundException $e) { throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e); } // find sabre nodes by file id, restricted to the root node path $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds); $filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath()); $responses = $this->prepareResponses($filesUri, $requestedProps, $results); $xml = $this->server->xml->write( '{DAV:}multistatus', new MultiStatus($responses) ); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setBody($xml); return false; } /** * Returns the base uri of the files root by removing * the subpath from the URI * * @param string $uri URI from this request * @param string $subPath subpath to remove from the URI * * @return string files base uri */ private function getFilesBaseUri($uri, $subPath) { $uri = trim($uri, '/'); $subPath = trim($subPath, '/'); if (empty($subPath)) { $filesUri = $uri; } else { $filesUri = substr($uri, 0, strlen($uri) - strlen($subPath)); } $filesUri = trim($filesUri, '/'); if (empty($filesUri)) { return ''; } return '/' . $filesUri; } /** * Find file ids matching the given filter rules * * @param array $filterRules * @return array array of unique file id results * * @throws TagNotFoundException whenever a tag was not found */ protected function processFilterRules($filterRules) { $ns = '{' . $this::NS_OWNCLOUD . '}'; $resultFileIds = null; $systemTagIds = []; $favoriteFilter = null; foreach ($filterRules as $filterRule) { if ($filterRule['name'] === $ns . 'systemtag') { $systemTagIds[] = $filterRule['value']; } if ($filterRule['name'] === $ns . 'favorite') { $favoriteFilter = true; } } if ($favoriteFilter !== null) { $resultFileIds = $this->fileTagger->load('files')->getFavorites(); if (empty($resultFileIds)) { return []; } } if (!empty($systemTagIds)) { $fileIds = $this->getSystemTagFileIds($systemTagIds); if (empty($resultFileIds)) { $resultFileIds = $fileIds; } else { $resultFileIds = array_intersect($fileIds, $resultFileIds); } } return $resultFileIds; } private function getSystemTagFileIds($systemTagIds) { $resultFileIds = null; // check user permissions, if applicable if (!$this->isAdmin()) { // check visibility/permission $tags = $this->tagManager->getTagsByIds($systemTagIds); $unknownTagIds = []; foreach ($tags as $tag) { if (!$tag->isUserVisible()) { $unknownTagIds[] = $tag->getId(); } } if (!empty($unknownTagIds)) { throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found'); } } // fetch all file ids and intersect them foreach ($systemTagIds as $systemTagId) { $fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files'); if (empty($fileIds)) { // This tag has no files, nothing can ever show up return []; } // first run ? if ($resultFileIds === null) { $resultFileIds = $fileIds; } else { $resultFileIds = array_intersect($resultFileIds, $fileIds); } if (empty($resultFileIds)) { // Empty intersection, nothing can show up anymore return []; } } return $resultFileIds; } /** * Prepare propfind response for the given nodes * * @param string $filesUri $filesUri URI leading to root of the files URI, * with a leading slash but no trailing slash * @param string[] $requestedProps requested properties * @param Node[] nodes nodes for which to fetch and prepare responses * @return Response[] */ public function prepareResponses($filesUri, $requestedProps, $nodes) { $responses = []; foreach ($nodes as $node) { $propFind = new PropFind($filesUri . $node->getPath(), $requestedProps); $this->server->getPropertiesByNode($propFind, $node); // copied from Sabre Server's getPropertiesForPath $result = $propFind->getResultForMultiStatus(); $result['href'] = $propFind->getPath(); $resourceType = $this->server->getResourceTypeForNode($node); if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { $result['href'] .= '/'; } $responses[] = new Response( rtrim($this->server->getBaseUri(), '/') . $filesUri . $node->getPath(), $result, 200 ); } return $responses; } /** * Find Sabre nodes by file ids * * @param Node $rootNode root node for search * @param array $fileIds file ids * @return Node[] array of Sabre nodes */ public function findNodesByFileIds($rootNode, $fileIds) { $folder = $this->userFolder; if (trim($rootNode->getPath(), '/') !== '') { $folder = $folder->get($rootNode->getPath()); } $results = []; foreach ($fileIds as $fileId) { $entry = $folder->getById($fileId); if ($entry) { $entry = current($entry); if ($entry instanceof \OCP\Files\File) { $results[] = new File($this->fileView, $entry); } else if ($entry instanceof \OCP\Files\Folder) { $results[] = new Directory($this->fileView, $entry); } } } return $results; } /** * Returns whether the currently logged in user is an administrator */ private function isAdmin() { $user = $this->userSession->getUser(); if ($user !== null) { return $this->groupManager->isAdmin($user->getUID()); } return false; } } lib/Connector/Sabre/CopyEtagHeaderPlugin.php 0000604 00000004454 15247100403 0015013 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; /** * Copies the "Etag" header to "OC-Etag" after any request. * This is a workaround for setups that automatically strip * or mangle Etag headers. */ class CopyEtagHeaderPlugin extends \Sabre\DAV\ServerPlugin { /** @var \Sabre\DAV\Server */ private $server; /** * This initializes the plugin. * * @param \Sabre\DAV\Server $server Sabre server * * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $server->on('afterMethod', [$this, 'afterMethod']); $server->on('afterMove', [$this, 'afterMove']); } /** * After method, copy the "Etag" header to "OC-Etag" header. * * @param RequestInterface $request request * @param ResponseInterface $response response */ public function afterMethod(RequestInterface $request, ResponseInterface $response) { $eTag = $response->getHeader('Etag'); if (!empty($eTag)) { $response->setHeader('OC-ETag', $eTag); } } /** * Called after a node is moved. * * This allows the backend to move all the associated properties. * * @param string $source * @param string $destination * @return void */ function afterMove($source, $destination) { $node = $this->server->tree->getNodeForPath($destination); if ($node instanceof File) { $eTag = $node->getETag(); $this->server->httpResponse->setHeader('OC-ETag', $eTag); $this->server->httpResponse->setHeader('ETag', $eTag); } } } lib/Connector/Sabre/ObjectTree.php 0000604 00000016053 15247100403 0013034 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OC\Files\FileInfo; use OC\Files\Mount\MoveableMount; use OCP\Files\ForbiddenException; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; use OCP\Lock\LockedException; class ObjectTree extends \Sabre\DAV\Tree { /** * @var \OC\Files\View */ protected $fileView; /** * @var \OCP\Files\Mount\IMountManager */ protected $mountManager; /** * Creates the object */ public function __construct() { } /** * @param \Sabre\DAV\INode $rootNode * @param \OC\Files\View $view * @param \OCP\Files\Mount\IMountManager $mountManager */ public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) { $this->rootNode = $rootNode; $this->fileView = $view; $this->mountManager = $mountManager; } /** * If the given path is a chunked file name, converts it * to the real file name. Only applies if the OC-CHUNKED header * is present. * * @param string $path chunk file path to convert * * @return string path to real file */ private function resolveChunkFile($path) { if (isset($_SERVER['HTTP_OC_CHUNKED'])) { // resolve to real file name to find the proper node list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path); if ($dir == '/' || $dir == '.') { $dir = ''; } $info = \OC_FileChunking::decodeName($name); // only replace path if it was really the chunked file if (isset($info['transferid'])) { // getNodePath is called for multiple nodes within a chunk // upload call $path = $dir . '/' . $info['name']; $path = ltrim($path, '/'); } } return $path; } public function cacheNode(Node $node) { $this->cache[trim($node->getPath(), '/')] = $node; } /** * Returns the INode object for the requested path * * @param string $path * @return \Sabre\DAV\INode * @throws InvalidPath * @throws \Sabre\DAV\Exception\Locked * @throws \Sabre\DAV\Exception\NotFound * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function getNodeForPath($path) { if (!$this->fileView) { throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); } $path = trim($path, '/'); if (isset($this->cache[$path])) { return $this->cache[$path]; } if ($path) { try { $this->fileView->verifyPath($path, basename($path)); } catch (\OCP\Files\InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } } // Is it the root node? if (!strlen($path)) { return $this->rootNode; } if (pathinfo($path, PATHINFO_EXTENSION) === 'part') { // read from storage $absPath = $this->fileView->getAbsolutePath($path); $mount = $this->fileView->getMount($path); $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($absPath); if ($storage && $storage->file_exists($internalPath)) { /** * @var \OC\Files\Storage\Storage $storage */ // get data directly $data = $storage->getMetaData($internalPath); $info = new FileInfo($absPath, $storage, $internalPath, $data, $mount); } else { $info = null; } } else { // resolve chunk file name to real name, if applicable $path = $this->resolveChunkFile($path); // read from cache try { $info = $this->fileView->getFileInfo($path); } catch (StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage is temporarily not available'); } catch (StorageInvalidException $e) { throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); } catch (LockedException $e) { throw new \Sabre\DAV\Exception\Locked(); } catch (ForbiddenException $e) { throw new \Sabre\DAV\Exception\Forbidden(); } } if (!$info) { throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); } if ($info->getType() === 'dir') { $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this); } else { $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); } $this->cache[$path] = $node; return $node; } /** * Copies a file or directory. * * This method must work recursively and delete the destination * if it exists * * @param string $source * @param string $destination * @throws FileLocked * @throws Forbidden * @throws InvalidPath * @throws \Exception * @throws \Sabre\DAV\Exception\Forbidden * @throws \Sabre\DAV\Exception\Locked * @throws \Sabre\DAV\Exception\NotFound * @throws \Sabre\DAV\Exception\ServiceUnavailable * @return void */ public function copy($source, $destination) { if (!$this->fileView) { throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); } $info = $this->fileView->getFileInfo(dirname($destination)); if ($this->fileView->file_exists($destination)) { $destinationPermission = $info && $info->isUpdateable(); } else { $destinationPermission = $info && $info->isCreatable(); } if (!$destinationPermission) { throw new Forbidden('No permissions to copy object.'); } // this will trigger existence check $this->getNodeForPath($source); list($destinationDir, $destinationName) = \Sabre\HTTP\URLUtil::splitPath($destination); try { $this->fileView->verifyPath($destinationDir, $destinationName); } catch (\OCP\Files\InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } try { $this->fileView->copy($source, $destination); } catch (StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); $this->markDirty($destinationDir); } } lib/Connector/Sabre/Exception/PasswordLoginForbidden.php 0000604 00000003060 15247100403 0017346 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; use DOMElement; use Sabre\DAV\Server; use Sabre\DAV\Exception\NotAuthenticated; class PasswordLoginForbidden extends NotAuthenticated { const NS_OWNCLOUD = 'http://owncloud.org/ns'; public function getHTTPCode() { return 401; } /** * This method allows the exception to include additional information * into the WebDAV error response * * @param Server $server * @param DOMElement $errorNode * @return void */ public function serialize(Server $server, DOMElement $errorNode) { // set ownCloud namespace $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); $error = $errorNode->ownerDocument->createElementNS('o:', 'o:hint', 'password login forbidden'); $errorNode->appendChild($error); } } lib/Connector/Sabre/Exception/EntityTooLarge.php 0000604 00000002304 15247100403 0015647 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; /** * Entity Too Large * * This exception is thrown whenever a user tries to upload a file which exceeds hard limitations * */ class EntityTooLarge extends \Sabre\DAV\Exception { /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 413; } } lib/Connector/Sabre/Exception/FileLocked.php 0000604 00000002731 15247100403 0014743 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Owen Winkler <a_github@midnightcircus.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; use Exception; class FileLocked extends \Sabre\DAV\Exception { public function __construct($message = "", $code = 0, Exception $previous = null) { if($previous instanceof \OCP\Files\LockNotAcquiredException) { $message = sprintf('Target file %s is locked by another process.', $previous->path); } parent::__construct($message, $code, $previous); } /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 423; } } lib/Connector/Sabre/Exception/InvalidPath.php 0000604 00000003672 15247100403 0015152 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; use Sabre\DAV\Exception; class InvalidPath extends Exception { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * @var bool */ private $retry; /** * @param string $message * @param bool $retry */ public function __construct($message, $retry = false) { parent::__construct($message); $this->retry = $retry; } /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 400; } /** * This method allows the exception to include additional information * into the WebDAV error response * * @param \Sabre\DAV\Server $server * @param \DOMElement $errorNode * @return void */ public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) { // set ownCloud namespace $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); // adding the retry node $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true)); $errorNode->appendChild($error); // adding the message node $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage()); $errorNode->appendChild($error); } } lib/Connector/Sabre/Exception/UnsupportedMediaType.php 0000604 00000002332 15247100403 0017071 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; /** * Unsupported Media Type * * This exception is thrown whenever a user tries to upload a file which holds content which is not allowed * */ class UnsupportedMediaType extends \Sabre\DAV\Exception { /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 415; } } lib/Connector/Sabre/Exception/Forbidden.php 0000604 00000003563 15247100403 0014642 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; class Forbidden extends \Sabre\DAV\Exception\Forbidden { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * @var bool */ private $retry; /** * @param string $message * @param bool $retry * @param \Exception $previous */ public function __construct($message, $retry = false, \Exception $previous = null) { parent::__construct($message, 0, $previous); $this->retry = $retry; } /** * This method allows the exception to include additional information * into the WebDAV error response * * @param \Sabre\DAV\Server $server * @param \DOMElement $errorNode * @return void */ public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) { // set ownCloud namespace $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); // adding the retry node $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true)); $errorNode->appendChild($error); // adding the message node $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage()); $errorNode->appendChild($error); } } lib/Connector/Sabre/File.php 0000604 00000046711 15247100403 0011671 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Owen Winkler <a_github@midnightcircus.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\Filesystem; use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException; use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType; use OCP\Encryption\Exceptions\GenericEncryptionException; use OCP\Files\EntityTooLargeException; use OCP\Files\ForbiddenException; use OCP\Files\InvalidContentException; use OCP\Files\InvalidPathException; use OCP\Files\LockNotAcquiredException; use OCP\Files\NotPermittedException; use OCP\Files\StorageNotAvailableException; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotImplemented; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\IFile; use Sabre\DAV\Exception\NotFound; class File extends Node implements IFile { /** * Updates the data * * The data argument is a readable stream resource. * * After a successful put operation, you may choose to return an ETag. The * etag must always be surrounded by double-quotes. These quotes must * appear in the actual string you're returning. * * Clients may use the ETag from a PUT request to later on make sure that * when they update the file, the contents haven't changed in the mean * time. * * If you don't plan to store the file byte-by-byte, and you return a * different object on a subsequent GET you are strongly recommended to not * return an ETag, and just return null. * * @param resource $data * * @throws Forbidden * @throws UnsupportedMediaType * @throws BadRequest * @throws Exception * @throws EntityTooLarge * @throws ServiceUnavailable * @throws FileLocked * @return string|null */ public function put($data) { try { $exists = $this->fileView->file_exists($this->path); if ($this->info && $exists && !$this->info->isUpdateable()) { throw new Forbidden(); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("File is not updatable: " . $e->getMessage()); } // verify path of the target $this->verifyPath(); // chunked handling if (isset($_SERVER['HTTP_OC_CHUNKED'])) { try { return $this->createFileChunked($data); } catch (\Exception $e) { $this->convertToSabreException($e); } } list($partStorage) = $this->fileView->resolvePath($this->path); $needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1); if ($needsPartFile) { // mark file as partial while uploading (ignored by the scanner) $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part'; } else { // upload file directly as the final path $partFilePath = $this->path; } // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share) /** @var \OC\Files\Storage\Storage $partStorage */ list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath); /** @var \OC\Files\Storage\Storage $storage */ list($storage, $internalPath) = $this->fileView->resolvePath($this->path); try { $target = $partStorage->fopen($internalPartPath, 'wb'); if ($target === false) { \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR); // because we have no clue about the cause we can only throw back a 500/Internal Server Error throw new Exception('Could not write file contents'); } list($count, $result) = \OC_Helper::streamCopy($data, $target); fclose($target); if ($result === false) { $expected = -1; if (isset($_SERVER['CONTENT_LENGTH'])) { $expected = $_SERVER['CONTENT_LENGTH']; } throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )'); } // if content length is sent by client: // double check if the file was fully received // compare expected and actual size if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { $expected = $_SERVER['CONTENT_LENGTH']; if ($count != $expected) { throw new BadRequest('expected filesize ' . $expected . ' got ' . $count); } } } catch (\Exception $e) { if ($needsPartFile) { $partStorage->unlink($internalPartPath); } $this->convertToSabreException($e); } try { $view = \OC\Files\Filesystem::getView(); if ($view) { $run = $this->emitPreHooks($exists); } else { $run = true; } try { $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); } catch (LockedException $e) { if ($needsPartFile) { $partStorage->unlink($internalPartPath); } throw new FileLocked($e->getMessage(), $e->getCode(), $e); } if ($needsPartFile) { // rename to correct path try { if ($run) { $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); $fileExists = $storage->file_exists($internalPath); } if (!$run || $renameOkay === false || $fileExists === false) { \OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', \OCP\Util::ERROR); throw new Exception('Could not rename part file to final file'); } } catch (ForbiddenException $ex) { throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (\Exception $e) { $partStorage->unlink($internalPartPath); $this->convertToSabreException($e); } } // since we skipped the view we need to scan and emit the hooks ourselves $storage->getUpdater()->update($internalPath); try { $this->changeLock(ILockingProvider::LOCK_SHARED); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } // allow sync clients to send the mtime along in a header $request = \OC::$server->getRequest(); if (isset($request->server['HTTP_X_OC_MTIME'])) { $mtimeStr = $request->server['HTTP_X_OC_MTIME']; if (!is_numeric($mtimeStr)) { throw new \InvalidArgumentException('X-OC-Mtime header must be an integer (unix timestamp).'); } $mtime = intval($mtimeStr); if ($this->fileView->touch($this->path, $mtime)) { header('X-OC-MTime: accepted'); } } if ($view) { $this->emitPostHooks($exists); } $this->refreshInfo(); if (isset($request->server['HTTP_OC_CHECKSUM'])) { $checksum = trim($request->server['HTTP_OC_CHECKSUM']); $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]); $this->refreshInfo(); } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') { $this->fileView->putFileInfo($this->path, ['checksum' => '']); $this->refreshInfo(); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage()); } return '"' . $this->info->getEtag() . '"'; } private function getPartFileBasePath($path) { $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true); if ($partFileInStorage) { return $path; } else { return md5($path); // will place it in the root of the view with a unique name } } /** * @param string $path */ private function emitPreHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); $run = true; if (!$exists) { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array( \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, )); } else { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array( \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, )); } \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array( \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, )); return $run; } /** * @param string $path */ private function emitPostHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); if (!$exists) { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array( \OC\Files\Filesystem::signal_param_path => $hookPath )); } else { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array( \OC\Files\Filesystem::signal_param_path => $hookPath )); } \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array( \OC\Files\Filesystem::signal_param_path => $hookPath )); } /** * Returns the data * * @return resource * @throws Forbidden * @throws ServiceUnavailable */ public function get() { //throw exception if encryption is disabled but files are still encrypted try { if (!$this->info->isReadable()) { // do a if the file did not exist throw new NotFound(); } $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb'); if ($res === false) { throw new ServiceUnavailable("Could not open file"); } return $res; } catch (GenericEncryptionException $e) { // returning 503 will allow retry of the operation at a later point in time throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage()); } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("Failed to open file: " . $e->getMessage()); } catch (ForbiddenException $ex) { throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Delete the current file * * @throws Forbidden * @throws ServiceUnavailable */ public function delete() { if (!$this->info->isDeletable()) { throw new Forbidden(); } try { if (!$this->fileView->unlink($this->path)) { // assume it wasn't possible to delete due to permissions throw new Forbidden(); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage()); } catch (ForbiddenException $ex) { throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream * * @return string */ public function getContentType() { $mimeType = $this->info->getMimetype(); // PROPFIND needs to return the correct mime type, for consistency with the web UI if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') { return $mimeType; } return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType); } /** * @return array|false */ public function getDirectDownload() { if (\OCP\App::isEnabled('encryption')) { return []; } /** @var \OCP\Files\Storage $storage */ list($storage, $internalPath) = $this->fileView->resolvePath($this->path); if (is_null($storage)) { return []; } return $storage->getDirectDownload($internalPath); } /** * @param resource $data * @return null|string * @throws Exception * @throws BadRequest * @throws NotImplemented * @throws ServiceUnavailable */ private function createFileChunked($data) { list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($this->path); $info = \OC_FileChunking::decodeName($name); if (empty($info)) { throw new NotImplemented('Invalid chunk name'); } $chunk_handler = new \OC_FileChunking($info); $bytesWritten = $chunk_handler->store($info['index'], $data); //detect aborted upload if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { if (isset($_SERVER['CONTENT_LENGTH'])) { $expected = $_SERVER['CONTENT_LENGTH']; if ($bytesWritten != $expected) { $chunk_handler->remove($info['index']); throw new BadRequest( 'expected filesize ' . $expected . ' got ' . $bytesWritten); } } } if ($chunk_handler->isComplete()) { list($storage,) = $this->fileView->resolvePath($path); $needsPartFile = $this->needsPartFile($storage); $partFile = null; $targetPath = $path . '/' . $info['name']; /** @var \OC\Files\Storage\Storage $targetStorage */ list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath); $exists = $this->fileView->file_exists($targetPath); try { $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED); $this->emitPreHooks($exists, $targetPath); $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE); /** @var \OC\Files\Storage\Storage $targetStorage */ list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath); if ($needsPartFile) { // we first assembly the target file as a part file $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part'; /** @var \OC\Files\Storage\Storage $targetStorage */ list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile); $chunk_handler->file_assemble($partStorage, $partInternalPath); // here is the final atomic rename $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath); $fileExists = $targetStorage->file_exists($targetInternalPath); if ($renameOkay === false || $fileExists === false) { \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR); // only delete if an error occurred and the target file was already created if ($fileExists) { // set to null to avoid double-deletion when handling exception // stray part file $partFile = null; $targetStorage->unlink($targetInternalPath); } $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); throw new Exception('Could not rename part file assembled from chunks'); } } else { // assemble directly into the final file $chunk_handler->file_assemble($targetStorage, $targetInternalPath); } // allow sync clients to send the mtime along in a header $request = \OC::$server->getRequest(); if (isset($request->server['HTTP_X_OC_MTIME'])) { if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) { header('X-OC-MTime: accepted'); } } // since we skipped the view we need to scan and emit the hooks ourselves $targetStorage->getUpdater()->update($targetInternalPath); $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); $this->emitPostHooks($exists, $targetPath); // FIXME: should call refreshInfo but can't because $this->path is not the of the final file $info = $this->fileView->getFileInfo($targetPath); if (isset($request->server['HTTP_OC_CHECKSUM'])) { $checksum = trim($request->server['HTTP_OC_CHECKSUM']); $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]); } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') { $this->fileView->putFileInfo($this->path, ['checksum' => '']); } $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED); return $info->getEtag(); } catch (\Exception $e) { if ($partFile !== null) { $targetStorage->unlink($targetInternalPath); } $this->convertToSabreException($e); } } return null; } /** * Returns whether a part file is needed for the given storage * or whether the file can be assembled/uploaded directly on the * target storage. * * @param \OCP\Files\Storage $storage * @return bool true if the storage needs part file handling */ private function needsPartFile($storage) { // TODO: in the future use ChunkHandler provided by storage return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') && !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') && $storage->needsPartFile(); } /** * Convert the given exception to a SabreException instance * * @param \Exception $e * * @throws \Sabre\DAV\Exception */ private function convertToSabreException(\Exception $e) { if ($e instanceof \Sabre\DAV\Exception) { throw $e; } if ($e instanceof NotPermittedException) { // a more general case - due to whatever reason the content could not be written throw new Forbidden($e->getMessage(), 0, $e); } if ($e instanceof ForbiddenException) { // the path for the file was forbidden throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e); } if ($e instanceof EntityTooLargeException) { // the file is too big to be stored throw new EntityTooLarge($e->getMessage(), 0, $e); } if ($e instanceof InvalidContentException) { // the file content is not permitted throw new UnsupportedMediaType($e->getMessage(), 0, $e); } if ($e instanceof InvalidPathException) { // the path for the file was not valid // TODO: find proper http status code for this case throw new Forbidden($e->getMessage(), 0, $e); } if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) { // the file is currently being written to by another process throw new FileLocked($e->getMessage(), $e->getCode(), $e); } if ($e instanceof GenericEncryptionException) { // returning 503 will allow retry of the operation at a later point in time throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e); } if ($e instanceof StorageNotAvailableException) { throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e); } throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e); } /** * Get the checksum for this file * * @return string */ public function getChecksum() { return $this->info->getChecksum(); } } lib/Connector/Sabre/DummyGetResponsePlugin.php 0000604 00000004224 15247100403 0015434 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\HTTP\ResponseInterface; use Sabre\HTTP\RequestInterface; /** * Class DummyGetResponsePlugin is a plugin used to not show a "Not implemented" * error to clients that rely on verifying the functionality of the Nextcloud * WebDAV backend using a simple GET to /. * * This is considered a legacy behaviour and implementers should consider sending * a PROPFIND request instead to verify whether the WebDAV component is working * properly. * * FIXME: Remove once clients are all compliant. * * @package OCA\DAV\Connector\Sabre */ class DummyGetResponsePlugin extends \Sabre\DAV\ServerPlugin { /** @var \Sabre\DAV\Server */ protected $server; /** * @param \Sabre\DAV\Server $server * @return void */ function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('method:GET', [$this, 'httpGet'], 200); } /** * @param RequestInterface $request * @param ResponseInterface $response * @return false */ function httpGet(RequestInterface $request, ResponseInterface $response) { $string = 'This is the WebDAV interface. It can only be accessed by ' . 'WebDAV clients such as the Nextcloud desktop sync client.'; $stream = fopen('php://memory','r+'); fwrite($stream, $string); rewind($stream); $response->setStatus(200); $response->setBody($stream); return false; } } lib/Connector/Sabre/Node.php 0000604 00000020440 15247100403 0011666 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Jakob Sack <mail@jakobsack.de> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Klaas Freitag <freitag@owncloud.com> * @author Markus Goetz <markus@woboq.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\Mount\MoveableMount; use OC\Files\View; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCP\Files\FileInfo; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; abstract class Node implements \Sabre\DAV\INode { /** * @var \OC\Files\View */ protected $fileView; /** * The path to the current node * * @var string */ protected $path; /** * node properties cache * * @var array */ protected $property_cache = null; /** * @var \OCP\Files\FileInfo */ protected $info; /** * @var IManager */ protected $shareManager; /** * Sets up the node, expects a full path name * * @param \OC\Files\View $view * @param \OCP\Files\FileInfo $info * @param IManager $shareManager */ public function __construct(View $view, FileInfo $info, IManager $shareManager = null) { $this->fileView = $view; $this->path = $this->fileView->getRelativePath($info->getPath()); $this->info = $info; if ($shareManager) { $this->shareManager = $shareManager; } else { $this->shareManager = \OC::$server->getShareManager(); } } protected function refreshInfo() { $this->info = $this->fileView->getFileInfo($this->path); } /** * Returns the name of the node * * @return string */ public function getName() { return $this->info->getName(); } /** * Returns the full path * * @return string */ public function getPath() { return $this->path; } /** * Renames the node * * @param string $name The new name * @throws \Sabre\DAV\Exception\BadRequest * @throws \Sabre\DAV\Exception\Forbidden */ public function setName($name) { // rename is only allowed if the update privilege is granted if (!$this->info->isUpdateable()) { throw new \Sabre\DAV\Exception\Forbidden(); } list($parentPath,) = \Sabre\HTTP\URLUtil::splitPath($this->path); list(, $newName) = \Sabre\HTTP\URLUtil::splitPath($name); // verify path of the target $this->verifyPath(); $newPath = $parentPath . '/' . $newName; $this->fileView->rename($this->path, $newPath); $this->path = $newPath; $this->refreshInfo(); } public function setPropertyCache($property_cache) { $this->property_cache = $property_cache; } /** * Returns the last modification time, as a unix timestamp * * @return int timestamp as integer */ public function getLastModified() { $timestamp = $this->info->getMtime(); if (!empty($timestamp)) { return (int)$timestamp; } return $timestamp; } /** * sets the last modification time of the file (mtime) to the value given * in the second parameter or to now if the second param is empty. * Even if the modification time is set to a custom value the access time is set to now. */ public function touch($mtime) { $this->fileView->touch($this->path, $mtime); $this->refreshInfo(); } /** * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the * file. If the file changes, the ETag MUST change. The ETag is an * arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined * * @return string */ public function getETag() { return '"' . $this->info->getEtag() . '"'; } /** * Sets the ETag * * @param string $etag * * @return int file id of updated file or -1 on failure */ public function setETag($etag) { return $this->fileView->putFileInfo($this->path, array('etag' => $etag)); } /** * Returns the size of the node, in bytes * * @return integer */ public function getSize() { return $this->info->getSize(); } /** * Returns the cache's file id * * @return int */ public function getId() { return $this->info->getId(); } /** * @return string|null */ public function getFileId() { if ($this->info->getId()) { $instanceId = \OC_Util::getInstanceId(); $id = sprintf('%08d', $this->info->getId()); return $id . $instanceId; } return null; } /** * @return integer */ public function getInternalFileId() { return $this->info->getId(); } /** * @param string $user * @return int */ public function getSharePermissions($user) { // check of we access a federated share if ($user !== null) { try { $share = $this->shareManager->getShareByToken($user); return $share->getPermissions(); } catch (ShareNotFound $e) { // ignore } } $storage = $this->info->getStorage(); $path = $this->info->getInternalPath(); if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { /** @var \OCA\Files_Sharing\SharedStorage $storage */ $permissions = (int)$storage->getShare()->getPermissions(); } else { $permissions = $storage->getPermissions($path); } /* * We can always share non moveable mount points with DELETE and UPDATE * Eventually we need to do this properly */ $mountpoint = $this->info->getMountPoint(); if (!($mountpoint instanceof MoveableMount)) { $mountpointpath = $mountpoint->getMountPoint(); if (substr($mountpointpath, -1) === '/') { $mountpointpath = substr($mountpointpath, 0, -1); } if ($mountpointpath === $this->info->getPath()) { $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE; } } /* * Files can't have create or delete permissions */ if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { $permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE); } return $permissions; } /** * @return string */ public function getDavPermissions() { $p = ''; if ($this->info->isShared()) { $p .= 'S'; } if ($this->info->isShareable()) { $p .= 'R'; } if ($this->info->isMounted()) { $p .= 'M'; } if ($this->info->isDeletable()) { $p .= 'D'; } if ($this->info->isUpdateable()) { $p .= 'NV'; // Renameable, Moveable } if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { if ($this->info->isUpdateable()) { $p .= 'W'; } } else { if ($this->info->isCreatable()) { $p .= 'CK'; } } return $p; } public function getOwner() { return $this->info->getOwner(); } protected function verifyPath() { try { $fileName = basename($this->info->getPath()); $this->fileView->verifyPath($this->path, $fileName); } catch (\OCP\Files\InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } } /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE */ public function acquireLock($type) { $this->fileView->lockFile($this->path, $type); } /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE */ public function releaseLock($type) { $this->fileView->unlockFile($this->path, $type); } /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE */ public function changeLock($type) { $this->fileView->changeLock($this->path, $type); } public function getFileInfo() { return $this->info; } } lib/Connector/Sabre/BlockLegacyClientPlugin.php 0000604 00000004671 15247100403 0015506 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IConfig; use Sabre\HTTP\RequestInterface; use Sabre\DAV\ServerPlugin; /** * Class BlockLegacyClientPlugin is used to detect old legacy sync clients and * returns a 403 status to those clients * * @package OCA\DAV\Connector\Sabre */ class BlockLegacyClientPlugin extends ServerPlugin { /** @var \Sabre\DAV\Server */ protected $server; /** @var IConfig */ protected $config; /** * @param IConfig $config */ public function __construct(IConfig $config) { $this->config = $config; } /** * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', [$this, 'beforeHandler'], 200); } /** * Detects all unsupported clients and throws a \Sabre\DAV\Exception\Forbidden * exception which will result in a 403 to them. * @param RequestInterface $request * @throws \Sabre\DAV\Exception\Forbidden If the client version is not supported */ public function beforeHandler(RequestInterface $request) { $userAgent = $request->getHeader('User-Agent'); if($userAgent === null) { return; } $minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '2.0.0'); // Match on the mirall version which is in scheme "Mozilla/5.0 (%1) mirall/%2" or // "mirall/%1" for older releases preg_match("/(?:mirall\\/)([\d.]+)/i", $userAgent, $versionMatches); if(isset($versionMatches[1]) && version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) { throw new \Sabre\DAV\Exception\Forbidden('Unsupported client version.'); } } } lib/Connector/Sabre/Directory.php 0000604 00000033715 15247100403 0012756 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCP\Files\FileInfo; use OCP\Files\ForbiddenException; use OCP\Files\InvalidPathException; use OCP\Files\StorageNotAvailableException; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception\Locked; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\INode; use Sabre\DAV\Exception\BadRequest; use OC\Files\Mount\MoveableMount; use Sabre\DAV\IFile; use Sabre\DAV\Exception\NotFound; class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget { /** * Cached directory content * * @var \OCP\Files\FileInfo[] */ private $dirContent; /** * Cached quota info * * @var array */ private $quotaInfo; /** * @var ObjectTree|null */ private $tree; /** * Sets up the node, expects a full path name * * @param \OC\Files\View $view * @param \OCP\Files\FileInfo $info * @param ObjectTree|null $tree * @param \OCP\Share\IManager $shareManager */ public function __construct(View $view, FileInfo $info, $tree = null, $shareManager = null) { parent::__construct($view, $info, $shareManager); $this->tree = $tree; } /** * Creates a new file in the directory * * Data will either be supplied as a stream resource, or in certain cases * as a string. Keep in mind that you may have to support either. * * After successful creation of the file, you may choose to return the ETag * of the new file here. * * The returned ETag must be surrounded by double-quotes (The quotes should * be part of the actual string). * * If you cannot accurately determine the ETag, you should not return it. * If you don't store the file exactly as-is (you're transforming it * somehow) you should also not return an ETag. * * This means that if a subsequent GET to this new file does not exactly * return the same contents of what was submitted here, you are strongly * recommended to omit the ETag. * * @param string $name Name of the file * @param resource|string $data Initial payload * @return null|string * @throws Exception\EntityTooLarge * @throws Exception\UnsupportedMediaType * @throws FileLocked * @throws InvalidPath * @throws \Sabre\DAV\Exception * @throws \Sabre\DAV\Exception\BadRequest * @throws \Sabre\DAV\Exception\Forbidden * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function createFile($name, $data = null) { try { // for chunked upload also updating a existing file is a "createFile" // because we create all the chunks before re-assemble them to the existing file. if (isset($_SERVER['HTTP_OC_CHUNKED'])) { // exit if we can't create a new file and we don't updatable existing file $chunkInfo = \OC_FileChunking::decodeName($name); if (!$this->fileView->isCreatable($this->path) && !$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name']) ) { throw new \Sabre\DAV\Exception\Forbidden(); } } else { // For non-chunked upload it is enough to check if we can create a new file if (!$this->fileView->isCreatable($this->path)) { throw new \Sabre\DAV\Exception\Forbidden(); } } $this->fileView->verifyPath($this->path, $name); $path = $this->fileView->getAbsolutePath($this->path) . '/' . $name; // in case the file already exists/overwriting $info = $this->fileView->getFileInfo($this->path . '/' . $name); if (!$info) { // use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete $info = new \OC\Files\FileInfo($path, null, null, [], null); } $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); $node->acquireLock(ILockingProvider::LOCK_SHARED); return $node->put($data); } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Creates a new subdirectory * * @param string $name * @throws FileLocked * @throws InvalidPath * @throws \Sabre\DAV\Exception\Forbidden * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function createDirectory($name) { try { if (!$this->info->isCreatable()) { throw new \Sabre\DAV\Exception\Forbidden(); } $this->fileView->verifyPath($this->path, $name); $newPath = $this->path . '/' . $name; if (!$this->fileView->mkdir($newPath)) { throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath); } } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Returns a specific child node, referenced by its name * * @param string $name * @param \OCP\Files\FileInfo $info * @return \Sabre\DAV\INode * @throws InvalidPath * @throws \Sabre\DAV\Exception\NotFound * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function getChild($name, $info = null) { if (!$this->info->isReadable()) { // avoid detecting files through this way throw new NotFound(); } $path = $this->path . '/' . $name; if (is_null($info)) { try { $this->fileView->verifyPath($this->path, $name); $info = $this->fileView->getFileInfo($path); } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $e) { throw new \Sabre\DAV\Exception\Forbidden(); } } if (!$info) { throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); } if ($info['mimetype'] == 'httpd/unix-directory') { $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager); } else { $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info, $this->shareManager); } if ($this->tree) { $this->tree->cacheNode($node); } return $node; } /** * Returns an array with all the child nodes * * @return \Sabre\DAV\INode[] * @throws \Sabre\DAV\Exception\Locked * @throws \OCA\DAV\Connector\Sabre\Exception\Forbidden */ public function getChildren() { if (!is_null($this->dirContent)) { return $this->dirContent; } try { if (!$this->info->isReadable()) { // return 403 instead of 404 because a 404 would make // the caller believe that the collection itself does not exist throw new Forbidden('No read permissions'); } $folderContent = $this->fileView->getDirectoryContent($this->path); } catch (LockedException $e) { throw new Locked(); } $nodes = array(); foreach ($folderContent as $info) { $node = $this->getChild($info->getName(), $info); $nodes[] = $node; } $this->dirContent = $nodes; return $this->dirContent; } /** * Checks if a child exists. * * @param string $name * @return bool */ public function childExists($name) { // note: here we do NOT resolve the chunk file name to the real file name // to make sure we return false when checking for file existence with a chunk // file name. // This is to make sure that "createFile" is still triggered // (required old code) instead of "updateFile". // // TODO: resolve chunk file name here and implement "updateFile" $path = $this->path . '/' . $name; return $this->fileView->file_exists($path); } /** * Deletes all files in this directory, and then itself * * @return void * @throws FileLocked * @throws \Sabre\DAV\Exception\Forbidden */ public function delete() { if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) { throw new \Sabre\DAV\Exception\Forbidden(); } try { if (!$this->fileView->rmdir($this->path)) { // assume it wasn't possible to remove due to permission issue throw new \Sabre\DAV\Exception\Forbidden(); } } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Returns available diskspace information * * @return array */ public function getQuotaInfo() { if ($this->quotaInfo) { return $this->quotaInfo; } try { $storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info); if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) { $free = \OCP\Files\FileInfo::SPACE_UNLIMITED; } else { $free = $storageInfo['free']; } $this->quotaInfo = array( $storageInfo['used'], $free ); return $this->quotaInfo; } catch (\OCP\Files\StorageNotAvailableException $e) { return array(0, 0); } } /** * Moves a node into this collection. * * It is up to the implementors to: * 1. Create the new resource. * 2. Remove the old resource. * 3. Transfer any properties or other data. * * Generally you should make very sure that your collection can easily move * the move. * * If you don't, just return false, which will trigger sabre/dav to handle * the move itself. If you return true from this function, the assumption * is that the move was successful. * * @param string $targetName New local file/collection name. * @param string $fullSourcePath Full path to source node * @param INode $sourceNode Source node itself * @return bool * @throws BadRequest * @throws ServiceUnavailable * @throws Forbidden * @throws FileLocked * @throws \Sabre\DAV\Exception\Forbidden */ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { if (!$sourceNode instanceof Node) { // it's a file of another kind, like FutureFile if ($sourceNode instanceof IFile) { // fallback to default copy+delete handling return false; } throw new BadRequest('Incompatible node types'); } if (!$this->fileView) { throw new ServiceUnavailable('filesystem not setup'); } $destinationPath = $this->getPath() . '/' . $targetName; $targetNodeExists = $this->childExists($targetName); // at getNodeForPath we also check the path for isForbiddenFileOrDir // with that we have covered both source and destination if ($sourceNode instanceof Directory && $targetNodeExists) { throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists'); } list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourceNode->getPath()); $destinationDir = $this->getPath(); $sourcePath = $sourceNode->getPath(); $isMovableMount = false; $sourceMount = \OC::$server->getMountManager()->find($this->fileView->getAbsolutePath($sourcePath)); $internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath)); if ($sourceMount instanceof MoveableMount && $internalPath === '') { $isMovableMount = true; } try { $sameFolder = ($sourceDir === $destinationDir); // if we're overwriting or same folder if ($targetNodeExists || $sameFolder) { // note that renaming a share mount point is always allowed if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) { throw new \Sabre\DAV\Exception\Forbidden(); } } else { if (!$this->fileView->isCreatable($destinationDir)) { throw new \Sabre\DAV\Exception\Forbidden(); } } if (!$sameFolder) { // moving to a different folder, source will be gone, like a deletion // note that moving a share mount point is always allowed if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) { throw new \Sabre\DAV\Exception\Forbidden(); } } $fileName = basename($destinationPath); try { $this->fileView->verifyPath($destinationDir, $fileName); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } $renameOkay = $this->fileView->rename($sourcePath, $destinationPath); if (!$renameOkay) { throw new \Sabre\DAV\Exception\Forbidden(''); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable($e->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } return true; } } lib/Connector/Sabre/DavAclPlugin.php 0000604 00000005306 15247100403 0013316 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\CalDAV\Principal\User; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\INode; use \Sabre\DAV\PropFind; /** * Class DavAclPlugin is a wrapper around \Sabre\DAVACL\Plugin that returns 404 * responses in case the resource to a response has been forbidden instead of * a 403. This is used to prevent enumeration of valid resources. * * @see https://github.com/owncloud/core/issues/22578 * @package OCA\DAV\Connector\Sabre */ class DavAclPlugin extends \Sabre\DAVACL\Plugin { public function __construct() { $this->hideNodesFromListings = true; $this->allowUnauthenticatedAccess = false; } function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { $access = parent::checkPrivileges($uri, $privileges, $recursion, false); if($access === false && $throwExceptions) { /** @var INode $node */ $node = $this->server->tree->getNodeForPath($uri); switch(get_class($node)) { case 'OCA\DAV\CardDAV\AddressBook': $type = 'Addressbook'; break; default: $type = 'Node'; break; } throw new NotFound( sprintf( "%s with name '%s' could not be found", $type, $node->getName() ) ); } return $access; } public function propFind(PropFind $propFind, INode $node) { // If the node is neither readable nor writable then fail unless its of // the standard user-principal if(!($node instanceof User)) { $path = $propFind->getPath(); $readPermissions = $this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false); $writePermissions = $this->checkPrivileges($path, '{DAV:}write', self::R_PARENT, false); if ($readPermissions === false && $writePermissions === false) { $this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, true); $this->checkPrivileges($path, '{DAV:}write', self::R_PARENT, true); } } return parent::propFind($propFind, $node); } } lib/Connector/Sabre/CustomPropertiesBackend.php 0000604 00000021710 15247100403 0015601 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Aaron Wood <aaronjwood@gmail.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IDBConnection; use OCP\IUser; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\DAV\Tree; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\ServiceUnavailable; class CustomPropertiesBackend implements BackendInterface { /** * Ignored properties * * @var array */ private $ignoredProperties = array( '{DAV:}getcontentlength', '{DAV:}getcontenttype', '{DAV:}getetag', '{DAV:}quota-used-bytes', '{DAV:}quota-available-bytes', '{DAV:}quota-available-bytes', '{http://owncloud.org/ns}permissions', '{http://owncloud.org/ns}downloadURL', '{http://owncloud.org/ns}dDC', '{http://owncloud.org/ns}size', ); /** * @var Tree */ private $tree; /** * @var IDBConnection */ private $connection; /** * @var IUser */ private $user; /** * Properties cache * * @var array */ private $cache = []; /** * @param Tree $tree node tree * @param IDBConnection $connection database connection * @param IUser $user owner of the tree and properties */ public function __construct( Tree $tree, IDBConnection $connection, IUser $user) { $this->tree = $tree; $this->connection = $connection; $this->user = $user->getUID(); } /** * Fetches properties for a path. * * @param string $path * @param PropFind $propFind * @return void */ public function propFind($path, PropFind $propFind) { try { $node = $this->tree->getNodeForPath($path); if (!($node instanceof Node)) { return; } } catch (ServiceUnavailable $e) { // might happen for unavailable mount points, skip return; } catch (NotFound $e) { // in some rare (buggy) cases the node might not be found, // we catch the exception to prevent breaking the whole list with a 404 // (soft fail) \OC::$server->getLogger()->warning( 'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(), array('app' => 'files') ); return; } $requestedProps = $propFind->get404Properties(); // these might appear $requestedProps = array_diff( $requestedProps, $this->ignoredProperties ); if (empty($requestedProps)) { return; } if ($node instanceof Directory && $propFind->getDepth() !== 0 ) { // note: pre-fetching only supported for depth <= 1 $this->loadChildrenProperties($node, $requestedProps); } $props = $this->getProperties($node, $requestedProps); foreach ($props as $propName => $propValue) { $propFind->set($propName, $propValue); } } /** * Updates properties for a path * * @param string $path * @param PropPatch $propPatch * * @return void */ public function propPatch($path, PropPatch $propPatch) { $node = $this->tree->getNodeForPath($path); if (!($node instanceof Node)) { return; } $propPatch->handleRemaining(function($changedProps) use ($node) { return $this->updateProperties($node, $changedProps); }); } /** * This method is called after a node is deleted. * * @param string $path path of node for which to delete properties */ public function delete($path) { $statement = $this->connection->prepare( 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute(array($this->user, '/' . $path)); $statement->closeCursor(); unset($this->cache[$path]); } /** * This method is called after a successful MOVE * * @param string $source * @param string $destination * * @return void */ public function move($source, $destination) { $statement = $this->connection->prepare( 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . ' WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute(array('/' . $destination, $this->user, '/' . $source)); $statement->closeCursor(); } /** * Returns a list of properties for this nodes.; * @param Node $node * @param array $requestedProperties requested properties or empty array for "all" * @return array * @note The properties list is a list of propertynames the client * requested, encoded as xmlnamespace#tagName, for example: * http://www.example.org/namespace#author If the array is empty, all * properties should be returned */ private function getProperties(Node $node, array $requestedProperties) { $path = $node->getPath(); if (isset($this->cache[$path])) { return $this->cache[$path]; } // TODO: chunking if more than 1000 properties $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; $whereValues = array($this->user, $path); $whereTypes = array(null, null); if (!empty($requestedProperties)) { // request only a subset $sql .= ' AND `propertyname` in (?)'; $whereValues[] = $requestedProperties; $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; } $result = $this->connection->executeQuery( $sql, $whereValues, $whereTypes ); $props = []; while ($row = $result->fetch()) { $props[$row['propertyname']] = $row['propertyvalue']; } $result->closeCursor(); $this->cache[$path] = $props; return $props; } /** * Update properties * * @param Node $node node for which to update properties * @param array $properties array of properties to update * * @return bool */ private function updateProperties($node, $properties) { $path = $node->getPath(); $deleteStatement = 'DELETE FROM `*PREFIX*properties`' . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; $insertStatement = 'INSERT INTO `*PREFIX*properties`' . ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'; $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; // TODO: use "insert or update" strategy ? $existing = $this->getProperties($node, array()); $this->connection->beginTransaction(); foreach ($properties as $propertyName => $propertyValue) { // If it was null, we need to delete the property if (is_null($propertyValue)) { if (array_key_exists($propertyName, $existing)) { $this->connection->executeUpdate($deleteStatement, array( $this->user, $path, $propertyName ) ); } } else { if (!array_key_exists($propertyName, $existing)) { $this->connection->executeUpdate($insertStatement, array( $this->user, $path, $propertyName, $propertyValue ) ); } else { $this->connection->executeUpdate($updateStatement, array( $propertyValue, $this->user, $path, $propertyName ) ); } } } $this->connection->commit(); unset($this->cache[$path]); return true; } /** * Bulk load properties for directory children * * @param Directory $node * @param array $requestedProperties requested properties * * @return void */ private function loadChildrenProperties(Directory $node, $requestedProperties) { $path = $node->getPath(); if (isset($this->cache[$path])) { // we already loaded them at some point return; } $childNodes = $node->getChildren(); // pre-fill cache foreach ($childNodes as $childNode) { $this->cache[$childNode->getPath()] = []; } $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?'; $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`'; $result = $this->connection->executeQuery( $sql, array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties), array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY) ); $oldPath = null; $props = []; while ($row = $result->fetch()) { $path = $row['propertypath']; if ($oldPath !== $path) { // save previously gathered props $this->cache[$oldPath] = $props; $oldPath = $path; // prepare props for next path $props = []; } $props[$row['propertyname']] = $row['propertyvalue']; } if (!is_null($oldPath)) { // save props from last run $this->cache[$oldPath] = $props; } $result->closeCursor(); } } lib/Connector/Sabre/FakeLockerPlugin.php 0000604 00000010165 15247100403 0014171 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\DAV\Locks\LockInfo; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Xml\Property\LockDiscovery; use Sabre\DAV\Xml\Property\SupportedLock; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\DAV\PropFind; use Sabre\DAV\INode; /** * Class FakeLockerPlugin is a plugin only used when connections come in from * OS X via Finder. The fake locking plugin does emulate Class 2 WebDAV support * (locking of files) which allows Finder to access the storage in write mode as * well. * * No real locking is performed, instead the plugin just returns always positive * responses. * * @see https://github.com/owncloud/core/issues/17732 * @package OCA\DAV\Connector\Sabre */ class FakeLockerPlugin extends ServerPlugin { /** @var \Sabre\DAV\Server */ private $server; /** {@inheritDoc} */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('method:LOCK', [$this, 'fakeLockProvider'], 1); $this->server->on('method:UNLOCK', [$this, 'fakeUnlockProvider'], 1); $server->on('propFind', [$this, 'propFind']); $server->on('validateTokens', [$this, 'validateTokens']); } /** * Indicate that we support LOCK and UNLOCK * * @param string $path * @return string[] */ public function getHTTPMethods($path) { return [ 'LOCK', 'UNLOCK', ]; } /** * Indicate that we support locking * * @return integer[] */ function getFeatures() { return [2]; } /** * Return some dummy response for PROPFIND requests with regard to locking * * @param PropFind $propFind * @param INode $node * @return void */ function propFind(PropFind $propFind, INode $node) { $propFind->handle('{DAV:}supportedlock', function() { return new SupportedLock(true); }); $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) { return new LockDiscovery([]); }); } /** * Mark a locking token always as valid * * @param RequestInterface $request * @param array $conditions */ public function validateTokens(RequestInterface $request, &$conditions) { foreach($conditions as &$fileCondition) { if(isset($fileCondition['tokens'])) { foreach($fileCondition['tokens'] as &$token) { if(isset($token['token'])) { if(substr($token['token'], 0, 16) === 'opaquelocktoken:') { $token['validToken'] = true; } } } } } } /** * Fakes a successful LOCK * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ public function fakeLockProvider(RequestInterface $request, ResponseInterface $response) { $lockInfo = new LockInfo(); $lockInfo->token = md5($request->getPath()); $lockInfo->uri = $request->getPath(); $lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY; $lockInfo->timeout = 1800; $body = $this->server->xml->write('{DAV:}prop', [ '{DAV:}lockdiscovery' => new LockDiscovery([$lockInfo]) ]); $response->setStatus(200); $response->setBody($body); return false; } /** * Fakes a successful LOCK * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ public function fakeUnlockProvider(RequestInterface $request, ResponseInterface $response) { $response->setStatus(204); $response->setHeader('Content-Length', '0'); return false; } } lib/Connector/Sabre/AppEnabledPlugin.php 0000604 00000004322 15247100403 0014154 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\App\IAppManager; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ServerPlugin; /** * Plugin to check if an app is enabled for the current user */ class AppEnabledPlugin extends ServerPlugin { /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var string */ private $app; /** * @var \OCP\App\IAppManager */ private $appManager; /** * @param string $app * @param \OCP\App\IAppManager $appManager */ public function __construct($app, IAppManager $appManager) { $this->app = $app; $this->appManager = $appManager; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', array($this, 'checkAppEnabled'), 30); } /** * This method is called before any HTTP after auth and checks if the user has access to the app * * @throws \Sabre\DAV\Exception\Forbidden * @return bool */ public function checkAppEnabled() { if (!$this->appManager->isEnabledForUser($this->app)) { throw new Forbidden(); } } } lib/Connector/PublicAuth.php 0000604 00000007051 15247100403 0012010 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector; use OCP\IRequest; use OCP\ISession; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use Sabre\DAV\Auth\Backend\AbstractBasic; /** * Class PublicAuth * * @package OCA\DAV\Connector */ class PublicAuth extends AbstractBasic { /** @var \OCP\Share\IShare */ private $share; /** @var IManager */ private $shareManager; /** @var ISession */ private $session; /** @var IRequest */ private $request; /** * @param IRequest $request * @param IManager $shareManager * @param ISession $session */ public function __construct(IRequest $request, IManager $shareManager, ISession $session) { $this->request = $request; $this->shareManager = $shareManager; $this->session = $session; // setup realm $defaults = new \OCP\Defaults(); $this->realm = $defaults->getName(); } /** * Validates a username and password * * This method should return true or false depending on if login * succeeded. * * @param string $username * @param string $password * * @return bool * @throws \Sabre\DAV\Exception\NotAuthenticated */ protected function validateUserPass($username, $password) { try { $share = $this->shareManager->getShareByToken($username); } catch (ShareNotFound $e) { return false; } $this->share = $share; \OC_User::setIncognitoMode(true); // check if the share is password protected if ($share->getPassword() !== null) { if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK || $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { if ($this->shareManager->checkPassword($share, $password)) { return true; } else if ($this->session->exists('public_link_authenticated') && $this->session->get('public_link_authenticated') === (string)$share->getId()) { return true; } else { if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) { // do not re-authenticate over ajax, use dummy auth name to prevent browser popup http_response_code(401); header('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"'); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } return false; } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) { return true; } else { return false; } } else { return true; } } /** * @return \OCP\Share\IShare */ public function getShare() { return $this->share; } } lib/Connector/LegacyDAVACL.php 0000604 00000003753 15247100403 0012034 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector; use OCA\DAV\Connector\Sabre\DavAclPlugin; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\HTTP\URLUtil; use Sabre\DAVACL\Xml\Property\Principal; class LegacyDAVACL extends DavAclPlugin { /** * @inheritdoc */ public function getCurrentUserPrincipals() { $principalV2 = $this->getCurrentUserPrincipal(); if (is_null($principalV2)) return []; $principalV1 = $this->convertPrincipal($principalV2, false); return array_merge( [ $principalV2, $principalV1 ], $this->getPrincipalMembership($principalV1) ); } private function convertPrincipal($principal, $toV2) { list(, $name) = URLUtil::splitPath($principal); if ($toV2) { return "principals/users/$name"; } return "principals/$name"; } public function propFind(PropFind $propFind, INode $node) { /* Overload current-user-principal */ $propFind->handle('{DAV:}current-user-principal', function () { if ($url = parent::getCurrentUserPrincipal()) { return new Principal(Principal::HREF, $url . '/'); } else { return new Principal(Principal::UNAUTHENTICATED); } }); return parent::propFind($propFind, $node); } } lib/Migration/BuildCalendarSearchIndex.php 0000604 00000004424 15247100403 0014557 0 ustar 00 <?php /** * @copyright 2017 Georg Ehrke <oc.list@georgehrke.com> * * @author Georg Ehrke <oc.list@georgehrke.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Migration; use OCP\BackgroundJob\IJobList; use OCP\IConfig; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; class BuildCalendarSearchIndex implements IRepairStep { /** @var IDBConnection */ private $db; /** @var IJobList */ private $jobList; /** @var IConfig */ private $config; /** * @param IDBConnection $db * @param IJobList $jobList * @param IConfig $config */ public function __construct(IDBConnection $db, IJobList $jobList, IConfig $config) { $this->db = $db; $this->jobList = $jobList; $this->config = $config; } /** * @return string */ public function getName() { return 'Registering building of calendar search index as background job'; } /** * @param IOutput $output */ public function run(IOutput $output) { // only run once if ($this->config->getAppValue('dav', 'buildCalendarSearchIndex') === 'yes') { $output->info('Repair step already executed'); return; } $query = $this->db->getQueryBuilder(); $query->select($query->createFunction('MAX(id)')) ->from('calendarobjects'); $maxId = (int)$query->execute()->fetchColumn(); $output->info('Add background job'); $this->jobList->add(BuildCalendarSearchIndexBackgroundJob::class, [ 'offset' => 0, 'stopAt' => $maxId ]); // if all were done, no need to redo the repair during next upgrade $this->config->setAppValue('dav', 'buildCalendarSearchIndex', 'yes'); } } lib/Migration/FixBirthdayCalendarComponent.php 0000604 00000003272 15247100403 0015502 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud GmbH. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Migration; use OCA\DAV\CalDAV\BirthdayService; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; class FixBirthdayCalendarComponent implements IRepairStep { /** @var IDBConnection */ private $connection; /** * FixBirthdayCalendarComponent constructor. * * @param IDBConnection $connection */ public function __construct(IDBConnection $connection) { $this->connection = $connection; } /** * @inheritdoc */ public function getName() { return 'Fix component of birthday calendars'; } /** * @inheritdoc */ public function run(IOutput $output) { $query = $this->connection->getQueryBuilder(); $updated = $query->update('calendars') ->set('components', $query->createNamedParameter('VEVENT')) ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI))) ->execute(); $output->info("$updated birthday calendars updated."); } } lib/Migration/CalDAVRemoveEmptyValue.php 0000604 00000006366 15247100403 0014203 0 ustar 00 <?php /** * @copyright 2017 Joas Schilling <coding@schilljs.com> * * @author Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Migration; use OCA\DAV\CalDAV\CalDavBackend; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\ILogger; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; use Sabre\VObject\InvalidDataException; class CalDAVRemoveEmptyValue implements IRepairStep { /** @var IDBConnection */ private $db; /** @var CalDavBackend */ private $calDavBackend; /** @var ILogger */ private $logger; /** * @param IDBConnection $db * @param CalDavBackend $calDavBackend * @param ILogger $logger */ public function __construct(IDBConnection $db, CalDavBackend $calDavBackend, ILogger $logger) { $this->db = $db; $this->calDavBackend = $calDavBackend; $this->logger = $logger; } public function getName() { return 'Fix broken values of calendar objects'; } public function run(IOutput $output) { $pattern = ';VALUE=:'; $count = $warnings = 0; $objects = $this->getInvalidObjects($pattern); $output->startProgress(count($objects)); foreach ($objects as $row) { $calObject = $this->calDavBackend->getCalendarObject((int)$row['calendarid'], $row['uri']); $data = preg_replace('/' . $pattern . '/', ':', $calObject['calendardata']); if ($data !== $calObject['calendardata']) { $output->advance(); try { $this->calDavBackend->getDenormalizedData($data); } catch (InvalidDataException $e) { $this->logger->info('Calendar object for calendar {cal} with uri {uri} still invalid', [ 'app' => 'dav', 'cal' => (int)$row['calendarid'], 'uri' => $row['uri'], ]); $warnings++; continue; } $this->calDavBackend->updateCalendarObject((int)$row['calendarid'], $row['uri'], $data); $count++; } } $output->finishProgress(); if ($warnings > 0) { $output->warning(sprintf('%d events could not be updated, see log file for more information', $warnings)); } if ($count > 0) { $output->info(sprintf('Updated %d events', $count)); } } protected function getInvalidObjects($pattern) { $query = $this->db->getQueryBuilder(); $query->select(['calendarid', 'uri']) ->from('calendarobjects') ->where($query->expr()->like( 'calendardata', $query->createNamedParameter( '%' . $this->db->escapeLikeParameter($pattern) . '%', IQueryBuilder::PARAM_STR ), IQueryBuilder::PARAM_STR )); $result = $query->execute(); $rows = $result->fetchAll(); $result->closeCursor(); return $rows; } } lib/Migration/BuildCalendarSearchIndexBackgroundJob.php 0000604 00000006366 15247100403 0017221 0 ustar 00 <?php /** * @copyright 2017 Georg Ehrke <oc.list@georgehrke.com> * * @author Georg Ehrke <oc.list@georgehrke.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Migration; use OC\BackgroundJob\QueuedJob; use OCA\DAV\CalDAV\CalDavBackend; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\IDBConnection; use OCP\ILogger; class BuildCalendarSearchIndexBackgroundJob extends QueuedJob { /** @var IDBConnection */ private $db; /** @var CalDavBackend */ private $calDavBackend; /** @var ILogger */ private $logger; /** @var IJobList */ private $jobList; /** @var ITimeFactory */ private $timeFactory; /** * @param IDBConnection $db * @param CalDavBackend $calDavBackend * @param ILogger $logger * @param IJobList $jobList * @param ITimeFactory $timeFactory */ public function __construct(IDBConnection $db, CalDavBackend $calDavBackend, ILogger $logger, IJobList $jobList, ITimeFactory $timeFactory) { $this->db = $db; $this->calDavBackend = $calDavBackend; $this->logger = $logger; $this->jobList = $jobList; $this->timeFactory = $timeFactory; } public function run($arguments) { $offset = $arguments['offset']; $stopAt = $arguments['stopAt']; $this->logger->info('Building calendar index (' . $offset .'/' . $stopAt . ')'); $offset = $this->buildIndex($offset, $stopAt); if ($offset >= $stopAt) { $this->logger->info('Building calendar index done'); } else { $this->jobList->add(self::class, [ 'offset' => $offset, 'stopAt' => $stopAt ]); $this->logger->info('New building calendar index job scheduled with offset ' . $offset); } } /** * @param int $offset * @param int $stopAt * @return int */ private function buildIndex($offset, $stopAt) { $startTime = $this->timeFactory->getTime(); $query = $this->db->getQueryBuilder(); $query->select(['id', 'calendarid', 'uri', 'calendardata']) ->from('calendarobjects') ->where($query->expr()->lte('id', $query->createNamedParameter($stopAt))) ->andWhere($query->expr()->gt('id', $query->createNamedParameter($offset))) ->orderBy('id', 'ASC'); $stmt = $query->execute(); while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $offset = $row['id']; $calendarData = $row['calendardata']; if (is_resource($calendarData)) { $calendarData = stream_get_contents($calendarData); } $this->calDavBackend->updateProperties($row['calendarid'], $row['uri'], $calendarData); if (($this->timeFactory->getTime() - $startTime) > 15) { return $offset; } } return $stopAt; } } lib/DAV/PublicAuth.php 0000604 00000004703 15247100403 0010471 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud, Inc. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV; use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class PublicAuth implements BackendInterface { /** @var string[] */ private $publicURLs; public function __construct() { $this->publicURLs = [ 'public-calendars', 'principals/system/public' ]; } /** * When this method is called, the backend must check if authentication was * successful. * * The returned value must be one of the following * * [true, "principals/username"] * [false, "reason for failure"] * * If authentication was successful, it's expected that the authentication * backend returns a so-called principal url. * * Examples of a principal url: * * principals/admin * principals/user1 * principals/users/joe * principals/uid/123457 * * If you don't use WebDAV ACL (RFC3744) we recommend that you simply * return a string such as: * * principals/users/[username] * * @param RequestInterface $request * @param ResponseInterface $response * @return array */ function check(RequestInterface $request, ResponseInterface $response) { if ($this->isRequestPublic($request)) { return [true, "principals/system/public"]; } return [false, "No public access to this resource."]; } /** * @inheritdoc */ function challenge(RequestInterface $request, ResponseInterface $response) { } /** * @param RequestInterface $request * @return bool */ private function isRequestPublic(RequestInterface $request) { $url = $request->getPath(); $matchingUrls = array_filter($this->publicURLs, function ($publicUrl) use ($url) { return strpos($url, $publicUrl, 0) === 0; }); return !empty($matchingUrls); } } lib/DAV/Sharing/Xml/Invite.php 0000604 00000011517 15247100403 0012023 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV\Sharing\Xml; use OCA\DAV\DAV\Sharing\Plugin; use Sabre\Xml\Writer; use Sabre\Xml\XmlSerializable; /** * Invite property * * This property encodes the 'invite' property, as defined by * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/ * namespace. * * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class Invite implements XmlSerializable { /** * The list of users a calendar has been shared to. * * @var array */ protected $users; /** * The organizer contains information about the person who shared the * object. * * @var array|null */ protected $organizer; /** * Creates the property. * * Users is an array. Each element of the array has the following * properties: * * * href - Often a mailto: address * * commonName - Optional, for example a first and lastname for a user. * * status - One of the SharingPlugin::STATUS_* constants. * * readOnly - true or false * * summary - Optional, description of the share * * The organizer key is optional to specify. It's only useful when a * 'sharee' requests the sharing information. * * The organizer may have the following properties: * * href - Often a mailto: address. * * commonName - Optional human-readable name. * * firstName - Optional first name. * * lastName - Optional last name. * * If you wonder why these two structures are so different, I guess a * valid answer is that the current spec is still a draft. * * @param array $users */ function __construct(array $users, array $organizer = null) { $this->users = $users; $this->organizer = $organizer; } /** * Returns the list of users, as it was passed to the constructor. * * @return array */ function getValue() { return $this->users; } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { $cs = '{' . Plugin::NS_OWNCLOUD . '}'; if (!is_null($this->organizer)) { $writer->startElement($cs . 'organizer'); $writer->writeElement('{DAV:}href', $this->organizer['href']); if (isset($this->organizer['commonName']) && $this->organizer['commonName']) { $writer->writeElement($cs . 'common-name', $this->organizer['commonName']); } if (isset($this->organizer['firstName']) && $this->organizer['firstName']) { $writer->writeElement($cs . 'first-name', $this->organizer['firstName']); } if (isset($this->organizer['lastName']) && $this->organizer['lastName']) { $writer->writeElement($cs . 'last-name', $this->organizer['lastName']); } $writer->endElement(); // organizer } foreach ($this->users as $user) { $writer->startElement($cs . 'user'); $writer->writeElement('{DAV:}href', $user['href']); if (isset($user['commonName']) && $user['commonName']) { $writer->writeElement($cs . 'common-name', $user['commonName']); } $writer->writeElement($cs . 'invite-accepted'); $writer->startElement($cs . 'access'); if ($user['readOnly']) { $writer->writeElement($cs . 'read'); } else { $writer->writeElement($cs . 'read-write'); } $writer->endElement(); // access if (isset($user['summary']) && $user['summary']) { $writer->writeElement($cs . 'summary', $user['summary']); } $writer->endElement(); //user } } } lib/DAV/Sharing/Xml/ShareRequest.php 0000604 00000004744 15247100403 0013204 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV\Sharing\Xml; use OCA\DAV\DAV\Sharing\Plugin; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; class ShareRequest implements XmlDeserializable { public $set = []; public $remove = []; /** * Constructor * * @param array $set * @param array $remove */ function __construct(array $set, array $remove) { $this->set = $set; $this->remove = $remove; } static function xmlDeserialize(Reader $reader) { $elements = $reader->parseInnerTree([ '{' . Plugin::NS_OWNCLOUD. '}set' => 'Sabre\\Xml\\Element\\KeyValue', '{' . Plugin::NS_OWNCLOUD . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', ]); $set = []; $remove = []; foreach ($elements as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_OWNCLOUD . '}set' : $sharee = $elem['value']; $sumElem = '{' . Plugin::NS_OWNCLOUD . '}summary'; $commonName = '{' . Plugin::NS_OWNCLOUD . '}common-name'; $set[] = [ 'href' => $sharee['{DAV:}href'], 'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null, 'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null, 'readOnly' => !array_key_exists('{' . Plugin::NS_OWNCLOUD . '}read-write', $sharee), ]; break; case '{' . Plugin::NS_OWNCLOUD . '}remove' : $remove[] = $elem['value']['{DAV:}href']; break; } } return new self($set, $remove); } } lib/DAV/Sharing/Backend.php 0000604 00000014516 15247100403 0011356 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV\Sharing; use OCA\DAV\Connector\Sabre\Principal; use OCP\IDBConnection; class Backend { /** @var IDBConnection */ private $db; /** @var Principal */ private $principalBackend; /** @var string */ private $resourceType; const ACCESS_OWNER = 1; const ACCESS_READ_WRITE = 2; const ACCESS_READ = 3; /** * @param IDBConnection $db * @param Principal $principalBackend * @param string $resourceType */ public function __construct(IDBConnection $db, Principal $principalBackend, $resourceType) { $this->db = $db; $this->principalBackend = $principalBackend; $this->resourceType = $resourceType; } /** * @param IShareable $shareable * @param string[] $add * @param string[] $remove */ public function updateShares($shareable, $add, $remove) { foreach($add as $element) { $this->shareWith($shareable, $element); } foreach($remove as $element) { $this->unshare($shareable, $element); } } /** * @param IShareable $shareable * @param string $element */ private function shareWith($shareable, $element) { $user = $element['href']; $parts = explode(':', $user, 2); if ($parts[0] !== 'principal') { return; } // don't share with owner if ($shareable->getOwner() === $parts[1]) { return; } // remove the share if it already exists $this->unshare($shareable, $element['href']); $access = self::ACCESS_READ; if (isset($element['readOnly'])) { $access = $element['readOnly'] ? self::ACCESS_READ : self::ACCESS_READ_WRITE; } $query = $this->db->getQueryBuilder(); $query->insert('dav_shares') ->values([ 'principaluri' => $query->createNamedParameter($parts[1]), 'type' => $query->createNamedParameter($this->resourceType), 'access' => $query->createNamedParameter($access), 'resourceid' => $query->createNamedParameter($shareable->getResourceId()) ]); $query->execute(); } /** * @param $resourceId */ public function deleteAllShares($resourceId) { $query = $this->db->getQueryBuilder(); $query->delete('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId))) ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) ->execute(); } public function deleteAllSharesByUser($principaluri) { $query = $this->db->getQueryBuilder(); $query->delete('dav_shares') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principaluri))) ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) ->execute(); } /** * @param IShareable $shareable * @param string $element */ private function unshare($shareable, $element) { $parts = explode(':', $element, 2); if ($parts[0] !== 'principal') { return; } // don't share with owner if ($shareable->getOwner() === $parts[1]) { return; } $query = $this->db->getQueryBuilder(); $query->delete('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($shareable->getResourceId()))) ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1]))) ; $query->execute(); } /** * Returns the list of people whom this resource is shared with. * * Every element in this array should have the following properties: * * href - Often a mailto: address * * commonName - Optional, for example a first + last name * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. * * readOnly - boolean * * summary - Optional, a description for the share * * @param int $resourceId * @return array */ public function getShares($resourceId) { $query = $this->db->getQueryBuilder(); $result = $query->select(['principaluri', 'access']) ->from('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId))) ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) ->execute(); $shares = []; while($row = $result->fetch()) { $p = $this->principalBackend->getPrincipalByPath($row['principaluri']); $shares[]= [ 'href' => "principal:${row['principaluri']}", 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '', 'status' => 1, 'readOnly' => ($row['access'] == self::ACCESS_READ), '{http://owncloud.org/ns}principal' => $row['principaluri'], '{http://owncloud.org/ns}group-share' => is_null($p) ]; } return $shares; } /** * For shared resources the sharee is set in the ACL of the resource * * @param int $resourceId * @param array $acl * @return array */ public function applyShareAcl($resourceId, $acl) { $shares = $this->getShares($resourceId); foreach ($shares as $share) { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], 'protected' => true, ]; if (!$share['readOnly']) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], 'protected' => true, ]; } else if ($this->resourceType === 'calendar') { // Allow changing the properties of read only calendars, // so users can change the visibility. $acl[] = [ 'privilege' => '{DAV:}write-properties', 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], 'protected' => true, ]; } } return $acl; } } lib/DAV/Sharing/IShareable.php 0000604 00000004076 15247100403 0012026 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV\Sharing; use Sabre\DAV\INode; /** * This interface represents a dav resource that can be shared with other users. * */ interface IShareable extends INode { /** * Updates the list of shares. * * The first array is a list of people that are to be added to the * resource. * * Every element in the add array has the following properties: * * href - A url. Usually a mailto: address * * commonName - Usually a first and last name, or false * * summary - A description of the share, can also be false * * readOnly - A boolean value * * Every element in the remove array is just the address string. * * @param array $add * @param array $remove * @return void */ function updateShares(array $add, array $remove); /** * Returns the list of people whom this resource is shared with. * * Every element in this array should have the following properties: * * href - Often a mailto: address * * commonName - Optional, for example a first + last name * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. * * readOnly - boolean * * summary - Optional, a description for the share * * @return array */ function getShares(); /** * @return int */ public function getResourceId(); /** * @return string */ public function getOwner(); } lib/DAV/Sharing/Plugin.php 0000604 00000012313 15247100403 0011256 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV\Sharing; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\DAV\Sharing\Xml\Invite; use OCP\IRequest; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class Plugin extends ServerPlugin { const NS_OWNCLOUD = 'http://owncloud.org/ns'; const NS_NEXTCLOUD = 'http://nextcloud.com/ns'; /** @var Auth */ private $auth; /** @var IRequest */ private $request; /** * Plugin constructor. * * @param Auth $authBackEnd * @param IRequest $request */ public function __construct(Auth $authBackEnd, IRequest $request) { $this->auth = $authBackEnd; $this->request = $request; } /** * Reference to SabreDAV server object. * * @var \Sabre\DAV\Server */ protected $server; /** * This method should return a list of server-features. * * This is for example 'versioning' and is added to the DAV: header * in an OPTIONS response. * * @return string[] */ function getFeatures() { return ['oc-resource-sharing']; } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using Sabre\DAV\Server::getPlugin * * @return string */ function getPluginName() { return 'oc-resource-sharing'; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server * @return void */ function initialize(Server $server) { $this->server = $server; $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}share'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest'; $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}invite'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\Invite'; $this->server->on('method:POST', [$this, 'httpPost']); $this->server->on('propFind', [$this, 'propFind']); } /** * We intercept this to handle POST requests on a dav resource. * * @param RequestInterface $request * @param ResponseInterface $response * @return null|false */ function httpPost(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); // Only handling xml $contentType = $request->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) return; // Making sure the node exists try { $node = $this->server->tree->getNodeForPath($path); } catch (NotFound $e) { return; } $requestBody = $request->getBodyAsString(); // If this request handler could not deal with this POST request, it // will return 'null' and other plugins get a chance to handle the // request. // // However, we already requested the full body. This is a problem, // because a body can only be read once. This is why we preemptively // re-populated the request body with the existing data. $request->setBody($requestBody); $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); switch ($documentType) { // Dealing with the 'share' document, which modified invitees on a // calendar. case '{' . self::NS_OWNCLOUD . '}share' : // We can only deal with IShareableCalendar objects if (!$node instanceof IShareable) { return; } $this->server->transactionType = 'post-oc-resource-share'; // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { /** @var \Sabre\DAVACL\Plugin $acl */ $acl->checkPrivileges($path, '{DAV:}write'); } $node->updateShares($message->set, $message->remove); $response->setStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; } } /** * This event is triggered when properties are requested for a certain * node. * * This allows us to inject any properties early. * * @param PropFind $propFind * @param INode $node * @return void */ function propFind(PropFind $propFind, INode $node) { if ($node instanceof IShareable) { $propFind->handle('{' . Plugin::NS_OWNCLOUD . '}invite', function() use ($node) { return new Invite( $node->getShares() ); }); } } } lib/DAV/GroupPrincipalBackend.php 0000604 00000011131 15247100403 0012630 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use Sabre\DAV\Exception; use \Sabre\DAV\PropPatch; use Sabre\DAVACL\PrincipalBackend\BackendInterface; class GroupPrincipalBackend implements BackendInterface { const PRINCIPAL_PREFIX = 'principals/groups'; /** @var IGroupManager */ private $groupManager; /** * @param IGroupManager $IGroupManager */ public function __construct(IGroupManager $IGroupManager) { $this->groupManager = $IGroupManager; } /** * Returns a list of principals based on a prefix. * * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * * You are expected to return at least a 'uri' for every user, you can * return any additional properties if you wish so. Common properties are: * {DAV:}displayname * * @param string $prefixPath * @return string[] */ public function getPrincipalsByPrefix($prefixPath) { $principals = []; if ($prefixPath === self::PRINCIPAL_PREFIX) { foreach($this->groupManager->search('') as $user) { $principals[] = $this->groupToPrincipal($user); } } return $principals; } /** * Returns a specific principal, specified by it's path. * The returned structure should be the exact same as from * getPrincipalsByPrefix. * * @param string $path * @return array */ public function getPrincipalByPath($path) { $elements = explode('/', $path, 3); if ($elements[0] !== 'principals') { return null; } if ($elements[1] !== 'groups') { return null; } $name = urldecode($elements[2]); $group = $this->groupManager->get($name); if (!is_null($group)) { return $this->groupToPrincipal($group); } return null; } /** * Returns the list of members for a group-principal * * @param string $principal * @return string[] * @throws Exception */ public function getGroupMemberSet($principal) { $elements = explode('/', $principal); if ($elements[0] !== 'principals') { return []; } if ($elements[1] !== 'groups') { return []; } $name = $elements[2]; $group = $this->groupManager->get($name); if (is_null($group)) { return []; } return array_map(function($user) { return $this->userToPrincipal($user); }, $group->getUsers()); } /** * Returns the list of groups a principal is a member of * * @param string $principal * @return array * @throws Exception */ public function getGroupMembership($principal) { return []; } /** * Updates the list of group members for a group principal. * * The principals should be passed as a list of uri's. * * @param string $principal * @param string[] $members * @throws Exception */ public function setGroupMemberSet($principal, array $members) { throw new Exception('Setting members of the group is not supported yet'); } /** * @param string $path * @param PropPatch $propPatch * @return int */ function updatePrincipal($path, PropPatch $propPatch) { return 0; } /** * @param string $prefixPath * @param array $searchProperties * @param string $test * @return array */ function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { return []; } /** * @param string $uri * @param string $principalPrefix * @return string */ function findByUri($uri, $principalPrefix) { return ''; } /** * @param IGroup $group * @return array */ protected function groupToPrincipal($group) { $groupId = $group->getGID(); $principal = [ 'uri' => 'principals/groups/' . urlencode($groupId), '{DAV:}displayname' => $groupId, ]; return $principal; } /** * @param IUser $user * @return array */ protected function userToPrincipal($user) { $principal = [ 'uri' => 'principals/users/' . $user->getUID(), '{DAV:}displayname' => $user->getDisplayName(), ]; return $principal; } } lib/DAV/SystemPrincipalBackend.php 0000604 00000012561 15247100403 0013030 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV; use Sabre\DAVACL\PrincipalBackend\AbstractBackend; use Sabre\HTTP\URLUtil; class SystemPrincipalBackend extends AbstractBackend { /** * Returns a list of principals based on a prefix. * * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * * You are expected to return at least a 'uri' for every user, you can * return any additional properties if you wish so. Common properties are: * {DAV:}displayname * {http://sabredav.org/ns}email-address - This is a custom SabreDAV * field that's actually injected in a number of other properties. If * you have an email address, use this property. * * @param string $prefixPath * @return array */ function getPrincipalsByPrefix($prefixPath) { $principals = []; if ($prefixPath === 'principals/system') { $principals[] = [ 'uri' => 'principals/system/system', '{DAV:}displayname' => 'system', ]; $principals[] = [ 'uri' => 'principals/system/public', '{DAV:}displayname' => 'public', ]; } return $principals; } /** * Returns a specific principal, specified by it's path. * The returned structure should be the exact same as from * getPrincipalsByPrefix. * * @param string $path * @return array */ function getPrincipalByPath($path) { if ($path === 'principals/system/system') { $principal = [ 'uri' => 'principals/system/system', '{DAV:}displayname' => 'system', ]; return $principal; } if ($path === 'principals/system/public') { $principal = [ 'uri' => 'principals/system/public', '{DAV:}displayname' => 'public', ]; return $principal; } return null; } /** * Updates one ore more webdav properties on a principal. * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param string $path * @param \Sabre\DAV\PropPatch $propPatch * @return void */ function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { } /** * This method is used to search for principals matching a set of * properties. * * This search is specifically used by RFC3744's principal-property-search * REPORT. * * The actual search should be a unicode-non-case-sensitive search. The * keys in searchProperties are the WebDAV property names, while the values * are the property values to search on. * * By default, if multiple properties are submitted to this method, the * various properties should be combined with 'AND'. If $test is set to * 'anyof', it should be combined using 'OR'. * * This method should simply return an array with full principal uri's. * * If somebody attempted to search on a property the backend does not * support, you should simply return 0 results. * * You can also just return 0 results if you choose to not support * searching at all, but keep in mind that this may stop certain features * from working. * * @param string $prefixPath * @param array $searchProperties * @param string $test * @return array */ function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { return []; } /** * Returns the list of members for a group-principal * * @param string $principal * @return array */ function getGroupMemberSet($principal) { // TODO: for now the group principal has only one member, the user itself $principal = $this->getPrincipalByPath($principal); if (!$principal) { throw new \Sabre\DAV\Exception('Principal not found'); } return [$principal['uri']]; } /** * Returns the list of groups a principal is a member of * * @param string $principal * @return array */ function getGroupMembership($principal) { list($prefix, $name) = URLUtil::splitPath($principal); if ($prefix === 'principals/system') { $principal = $this->getPrincipalByPath($principal); if (!$principal) { throw new \Sabre\DAV\Exception('Principal not found'); } return []; } return []; } /** * Updates the list of group members for a group principal. * * The principals should be passed as a list of uri's. * * @param string $principal * @param array $members * @return void */ function setGroupMemberSet($principal, array $members) { throw new \Sabre\DAV\Exception('Setting members of the group is not supported yet'); } } lib/DAV/CustomPropertiesBackend.php 0000604 00000016611 15247100403 0013231 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017, Georg Ehrke <oc.list@georgehrke.com> * * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\DAV; use OCP\IDBConnection; use OCP\IUser; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\DAV\Tree; class CustomPropertiesBackend implements BackendInterface { /** * Ignored properties * * @var array */ private $ignoredProperties = array( '{DAV:}getcontentlength', '{DAV:}getcontenttype', '{DAV:}getetag', '{DAV:}quota-used-bytes', '{DAV:}quota-available-bytes', '{http://owncloud.org/ns}permissions', '{http://owncloud.org/ns}downloadURL', '{http://owncloud.org/ns}dDC', '{http://owncloud.org/ns}size', ); /** * @var Tree */ private $tree; /** * @var IDBConnection */ private $connection; /** * @var string */ private $user; /** * Properties cache * * @var array */ private $cache = []; /** * @param Tree $tree node tree * @param IDBConnection $connection database connection * @param IUser $user owner of the tree and properties */ public function __construct( Tree $tree, IDBConnection $connection, IUser $user) { $this->tree = $tree; $this->connection = $connection; $this->user = $user->getUID(); } /** * Fetches properties for a path. * * @param string $path * @param PropFind $propFind * @return void */ public function propFind($path, PropFind $propFind) { $requestedProps = $propFind->get404Properties(); // these might appear $requestedProps = array_diff( $requestedProps, $this->ignoredProperties ); // substr of calendars/ => path is inside the CalDAV component // two '/' => this a calendar (no calendar-home nor calendar object) if (substr($path, 0, 10) === 'calendars/' && substr_count($path, '/') === 2) { $allRequestedProps = $propFind->getRequestedProperties(); $customPropertiesForShares = [ '{DAV:}displayname', '{urn:ietf:params:xml:ns:caldav}calendar-description', '{urn:ietf:params:xml:ns:caldav}calendar-timezone', '{http://apple.com/ns/ical/}calendar-order', '{http://apple.com/ns/ical/}calendar-color', '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp', ]; foreach ($customPropertiesForShares as $customPropertyForShares) { if (in_array($customPropertyForShares, $allRequestedProps)) { $requestedProps[] = $customPropertyForShares; } } } if (empty($requestedProps)) { return; } $props = $this->getProperties($path, $requestedProps); foreach ($props as $propName => $propValue) { $propFind->set($propName, $propValue); } } /** * Updates properties for a path * * @param string $path * @param PropPatch $propPatch * * @return void */ public function propPatch($path, PropPatch $propPatch) { $propPatch->handleRemaining(function($changedProps) use ($path) { return $this->updateProperties($path, $changedProps); }); } /** * This method is called after a node is deleted. * * @param string $path path of node for which to delete properties */ public function delete($path) { $statement = $this->connection->prepare( 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute(array($this->user, $path)); $statement->closeCursor(); unset($this->cache[$path]); } /** * This method is called after a successful MOVE * * @param string $source * @param string $destination * * @return void */ public function move($source, $destination) { $statement = $this->connection->prepare( 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . ' WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute(array($destination, $this->user, $source)); $statement->closeCursor(); } /** * Returns a list of properties for this nodes.; * @param string $path * @param array $requestedProperties requested properties or empty array for "all" * @return array * @note The properties list is a list of propertynames the client * requested, encoded as xmlnamespace#tagName, for example: * http://www.example.org/namespace#author If the array is empty, all * properties should be returned */ private function getProperties($path, array $requestedProperties) { if (isset($this->cache[$path])) { return $this->cache[$path]; } // TODO: chunking if more than 1000 properties $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; $whereValues = array($this->user, $path); $whereTypes = array(null, null); if (!empty($requestedProperties)) { // request only a subset $sql .= ' AND `propertyname` in (?)'; $whereValues[] = $requestedProperties; $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; } $result = $this->connection->executeQuery( $sql, $whereValues, $whereTypes ); $props = []; while ($row = $result->fetch()) { $props[$row['propertyname']] = $row['propertyvalue']; } $result->closeCursor(); $this->cache[$path] = $props; return $props; } /** * Update properties * * @param string $path node for which to update properties * @param array $properties array of properties to update * * @return bool */ private function updateProperties($path, $properties) { $deleteStatement = 'DELETE FROM `*PREFIX*properties`' . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; $insertStatement = 'INSERT INTO `*PREFIX*properties`' . ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'; $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; // TODO: use "insert or update" strategy ? $existing = $this->getProperties($path, array()); $this->connection->beginTransaction(); foreach ($properties as $propertyName => $propertyValue) { // If it was null, we need to delete the property if (is_null($propertyValue)) { if (array_key_exists($propertyName, $existing)) { $this->connection->executeUpdate($deleteStatement, array( $this->user, $path, $propertyName ) ); } } else { if (!array_key_exists($propertyName, $existing)) { $this->connection->executeUpdate($insertStatement, array( $this->user, $path, $propertyName, $propertyValue ) ); } else { $this->connection->executeUpdate($updateStatement, array( $propertyValue, $this->user, $path, $propertyName ) ); } } } $this->connection->commit(); unset($this->cache[$path]); return true; } } lib/AppInfo/Application.php 0000604 00000014764 15247100403 0011626 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Georg Ehrke <georg@owncloud.com> * @author Joas Schilling <coding@schilljs.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\AppInfo; use OC\AppFramework\Utility\SimpleContainer; use OCA\DAV\CalDAV\Activity\Backend; use OCA\DAV\CalDAV\Activity\Provider\Event; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\Capabilities; use OCA\DAV\CardDAV\ContactsManager; use OCA\DAV\CardDAV\PhotoCache; use OCA\DAV\CardDAV\SyncService; use OCA\DAV\HookManager; use \OCP\AppFramework\App; use OCP\Contacts\IManager; use OCP\IUser; use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { /** * Application constructor. */ public function __construct() { parent::__construct('dav'); $container = $this->getContainer(); $server = $container->getServer(); $container->registerService(PhotoCache::class, function(SimpleContainer $s) use ($server) { return new PhotoCache( $server->getAppDataDir('dav-photocache') ); }); /* * Register capabilities */ $container->registerCapability(Capabilities::class); } /** * @param IManager $contactsManager * @param string $userID */ public function setupContactsProvider(IManager $contactsManager, $userID) { /** @var ContactsManager $cm */ $cm = $this->getContainer()->query(ContactsManager::class); $urlGenerator = $this->getContainer()->getServer()->getURLGenerator(); $cm->setupContactsProvider($contactsManager, $userID, $urlGenerator); } public function registerHooks() { /** @var HookManager $hm */ $hm = $this->getContainer()->query(HookManager::class); $hm->setup(); $dispatcher = $this->getContainer()->getServer()->getEventDispatcher(); // first time login event setup $dispatcher->addListener(IUser::class . '::firstLogin', function ($event) use ($hm) { if ($event instanceof GenericEvent) { $hm->firstLogin($event->getSubject()); } }); // carddav/caldav sync event setup $listener = function($event) { if ($event instanceof GenericEvent) { /** @var BirthdayService $b */ $b = $this->getContainer()->query(BirthdayService::class); $b->onCardChanged( $event->getArgument('addressBookId'), $event->getArgument('cardUri'), $event->getArgument('cardData') ); } }; $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $listener); $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $listener); $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function($event) { if ($event instanceof GenericEvent) { /** @var BirthdayService $b */ $b = $this->getContainer()->query(BirthdayService::class); $b->onCardDeleted( $event->getArgument('addressBookId'), $event->getArgument('cardUri') ); } }); $clearPhotoCache = function($event) { if ($event instanceof GenericEvent) { /** @var PhotoCache $p */ $p = $this->getContainer()->query(PhotoCache::class); $p->delete( $event->getArgument('addressBookId'), $event->getArgument('cardUri') ); } }; $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $clearPhotoCache); $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', $clearPhotoCache); $dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) { $user = $event->getSubject(); $syncService = $this->getContainer()->query(SyncService::class); $syncService->updateUser($user); }); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', function(GenericEvent $event) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); $backend->onCalendarAdd( $event->getArgument('calendarData') ); }); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', function(GenericEvent $event) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); $backend->onCalendarUpdate( $event->getArgument('calendarData'), $event->getArgument('shares'), $event->getArgument('propertyMutations') ); }); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', function(GenericEvent $event) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); $backend->onCalendarDelete( $event->getArgument('calendarData'), $event->getArgument('shares') ); }); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function(GenericEvent $event) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); $backend->onCalendarUpdateShares( $event->getArgument('calendarData'), $event->getArgument('shares'), $event->getArgument('add'), $event->getArgument('remove') ); }); $listener = function(GenericEvent $event, $eventName) { /** @var Backend $backend */ $backend = $this->getContainer()->query(Backend::class); $subject = Event::SUBJECT_OBJECT_ADD; if ($eventName === '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject') { $subject = Event::SUBJECT_OBJECT_UPDATE; } else if ($eventName === '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject') { $subject = Event::SUBJECT_OBJECT_DELETE; } $backend->onTouchCalendarObject( $subject, $event->getArgument('calendarData'), $event->getArgument('shares'), $event->getArgument('objectData') ); }; $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', $listener); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', $listener); $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', $listener); } public function getSyncService() { return $this->getContainer()->query(SyncService::class); } } lib/CardDAV/CardDavBackend.php 0000604 00000104074 15247100403 0012001 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Björn Schießle <bjoern@schiessle.org> * @author Georg Ehrke <georg@owncloud.com> * @author Joas Schilling <coding@schilljs.com> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCA\DAV\Connector\Sabre\Principal; use OCP\DB\QueryBuilder\IQueryBuilder; use OCA\DAV\DAV\Sharing\Backend; use OCA\DAV\DAV\Sharing\IShareable; use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; use PDO; use Sabre\CardDAV\Backend\BackendInterface; use Sabre\CardDAV\Backend\SyncSupport; use Sabre\CardDAV\Plugin; use Sabre\DAV\Exception\BadRequest; use Sabre\HTTP\URLUtil; use Sabre\VObject\Component\VCard; use Sabre\VObject\Reader; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; class CardDavBackend implements BackendInterface, SyncSupport { const PERSONAL_ADDRESSBOOK_URI = 'contacts'; const PERSONAL_ADDRESSBOOK_NAME = 'Contacts'; /** @var Principal */ private $principalBackend; /** @var string */ private $dbCardsTable = 'cards'; /** @var string */ private $dbCardsPropertiesTable = 'cards_properties'; /** @var IDBConnection */ private $db; /** @var Backend */ private $sharingBackend; /** @var array properties to index */ public static $indexProperties = array( 'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME', 'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'); /** * @var string[] Map of uid => display name */ protected $userDisplayNames; /** @var IUserManager */ private $userManager; /** @var EventDispatcherInterface */ private $dispatcher; /** * CardDavBackend constructor. * * @param IDBConnection $db * @param Principal $principalBackend * @param IUserManager $userManager * @param EventDispatcherInterface $dispatcher */ public function __construct(IDBConnection $db, Principal $principalBackend, IUserManager $userManager, EventDispatcherInterface $dispatcher) { $this->db = $db; $this->principalBackend = $principalBackend; $this->userManager = $userManager; $this->dispatcher = $dispatcher; $this->sharingBackend = new Backend($this->db, $principalBackend, 'addressbook'); } /** * Return the number of address books for a principal * * @param $principalUri * @return int */ public function getAddressBooksForUserCount($principalUri) { $principalUri = $this->convertPrincipal($principalUri, true); $query = $this->db->getQueryBuilder(); $query->select($query->createFunction('COUNT(*)')) ->from('addressbooks') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))); return (int)$query->execute()->fetchColumn(); } /** * Returns the list of address books for a specific user. * * Every addressbook should have the following properties: * id - an arbitrary unique id * uri - the 'basename' part of the url * principaluri - Same as the passed parameter * * Any additional clark-notation property may be passed besides this. Some * common ones are : * {DAV:}displayname * {urn:ietf:params:xml:ns:carddav}addressbook-description * {http://calendarserver.org/ns/}getctag * * @param string $principalUri * @return array */ function getAddressBooksForUser($principalUri) { $principalUriOriginal = $principalUri; $principalUri = $this->convertPrincipal($principalUri, true); $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken']) ->from('addressbooks') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))); $addressBooks = []; $result = $query->execute(); while($row = $result->fetch()) { $addressBooks[$row['id']] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], false), '{DAV:}displayname' => $row['displayname'], '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', ]; $this->addOwnerPrincipal($addressBooks[$row['id']]); } $result->closeCursor(); // query for shared calendars $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true); $principals = array_map(function($principal) { return urldecode($principal); }, $principals); $principals[]= $principalUri; $query = $this->db->getQueryBuilder(); $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access']) ->from('dav_shares', 's') ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri'))) ->andWhere($query->expr()->eq('s.type', $query->createParameter('type'))) ->setParameter('type', 'addressbook') ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY) ->execute(); $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only'; while($row = $result->fetch()) { if ($row['principaluri'] === $principalUri) { continue; } $readOnly = (int) $row['access'] === Backend::ACCESS_READ; if (isset($addressBooks[$row['id']])) { if ($readOnly) { // New share can not have more permissions then the old one. continue; } if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) && $addressBooks[$row['id']][$readOnlyPropertyName] === 0) { // Old share is already read-write, no more permissions can be gained continue; } } list(, $name) = URLUtil::splitPath($row['principaluri']); $uri = $row['uri'] . '_shared_by_' . $name; $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')'; $addressBooks[$row['id']] = [ 'id' => $row['id'], 'uri' => $uri, 'principaluri' => $principalUriOriginal, '{DAV:}displayname' => $displayName, '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'], $readOnlyPropertyName => $readOnly, ]; $this->addOwnerPrincipal($addressBooks[$row['id']]); } $result->closeCursor(); return array_values($addressBooks); } public function getUsersOwnAddressBooks($principalUri) { $principalUri = $this->convertPrincipal($principalUri, true); $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken']) ->from('addressbooks') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))); $addressBooks = []; $result = $query->execute(); while($row = $result->fetch()) { $addressBooks[$row['id']] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], false), '{DAV:}displayname' => $row['displayname'], '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', ]; $this->addOwnerPrincipal($addressBooks[$row['id']]); } $result->closeCursor(); return array_values($addressBooks); } private function getUserDisplayName($uid) { if (!isset($this->userDisplayNames[$uid])) { $user = $this->userManager->get($uid); if ($user instanceof IUser) { $this->userDisplayNames[$uid] = $user->getDisplayName(); } else { $this->userDisplayNames[$uid] = $uid; } } return $this->userDisplayNames[$uid]; } /** * @param int $addressBookId */ public function getAddressBookById($addressBookId) { $query = $this->db->getQueryBuilder(); $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken']) ->from('addressbooks') ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId))) ->execute(); $row = $result->fetch(); $result->closeCursor(); if ($row === false) { return null; } $addressBook = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $row['principaluri'], '{DAV:}displayname' => $row['displayname'], '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', ]; $this->addOwnerPrincipal($addressBook); return $addressBook; } /** * @param $addressBookUri * @return array|null */ public function getAddressBooksByUri($principal, $addressBookUri) { $query = $this->db->getQueryBuilder(); $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken']) ->from('addressbooks') ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri))) ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal))) ->setMaxResults(1) ->execute(); $row = $result->fetch(); $result->closeCursor(); if ($row === false) { return null; } $addressBook = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $row['principaluri'], '{DAV:}displayname' => $row['displayname'], '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', ]; $this->addOwnerPrincipal($addressBook); return $addressBook; } /** * Updates properties for an address book. * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param string $addressBookId * @param \Sabre\DAV\PropPatch $propPatch * @return void */ function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { $supportedProperties = [ '{DAV:}displayname', '{' . Plugin::NS_CARDDAV . '}addressbook-description', ]; $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) { $updates = []; foreach($mutations as $property=>$newValue) { switch($property) { case '{DAV:}displayname' : $updates['displayname'] = $newValue; break; case '{' . Plugin::NS_CARDDAV . '}addressbook-description' : $updates['description'] = $newValue; break; } } $query = $this->db->getQueryBuilder(); $query->update('addressbooks'); foreach($updates as $key=>$value) { $query->set($key, $query->createNamedParameter($value)); } $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId))) ->execute(); $this->addChange($addressBookId, "", 2); return true; }); } /** * Creates a new address book * * @param string $principalUri * @param string $url Just the 'basename' of the url. * @param array $properties * @return int * @throws BadRequest */ function createAddressBook($principalUri, $url, array $properties) { $values = [ 'displayname' => null, 'description' => null, 'principaluri' => $principalUri, 'uri' => $url, 'synctoken' => 1 ]; foreach($properties as $property=>$newValue) { switch($property) { case '{DAV:}displayname' : $values['displayname'] = $newValue; break; case '{' . Plugin::NS_CARDDAV . '}addressbook-description' : $values['description'] = $newValue; break; default : throw new BadRequest('Unknown property: ' . $property); } } // Fallback to make sure the displayname is set. Some clients may refuse // to work with addressbooks not having a displayname. if(is_null($values['displayname'])) { $values['displayname'] = $url; } $query = $this->db->getQueryBuilder(); $query->insert('addressbooks') ->values([ 'uri' => $query->createParameter('uri'), 'displayname' => $query->createParameter('displayname'), 'description' => $query->createParameter('description'), 'principaluri' => $query->createParameter('principaluri'), 'synctoken' => $query->createParameter('synctoken'), ]) ->setParameters($values) ->execute(); return $query->getLastInsertId(); } /** * Deletes an entire addressbook and all its contents * * @param mixed $addressBookId * @return void */ function deleteAddressBook($addressBookId) { $query = $this->db->getQueryBuilder(); $query->delete('cards') ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid'))) ->setParameter('addressbookid', $addressBookId) ->execute(); $query->delete('addressbookchanges') ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid'))) ->setParameter('addressbookid', $addressBookId) ->execute(); $query->delete('addressbooks') ->where($query->expr()->eq('id', $query->createParameter('id'))) ->setParameter('id', $addressBookId) ->execute(); $this->sharingBackend->deleteAllShares($addressBookId); $query->delete($this->dbCardsPropertiesTable) ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->execute(); } /** * Returns all cards for a specific addressbook id. * * This method should return the following properties for each card: * * carddata - raw vcard data * * uri - Some unique url * * lastmodified - A unix timestamp * * It's recommended to also return the following properties: * * etag - A unique etag. This must change every time the card changes. * * size - The size of the card in bytes. * * If these last two properties are provided, less time will be spent * calculating them. If they are specified, you can also ommit carddata. * This may speed up certain requests, especially with large cards. * * @param mixed $addressBookId * @return array */ function getCards($addressBookId) { $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata']) ->from('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); $cards = []; $result = $query->execute(); while($row = $result->fetch()) { $row['etag'] = '"' . $row['etag'] . '"'; $row['carddata'] = $this->readBlob($row['carddata']); $cards[] = $row; } $result->closeCursor(); return $cards; } /** * Returns a specific card. * * The same set of properties must be returned as with getCards. The only * exception is that 'carddata' is absolutely required. * * If the card does not exist, you must return false. * * @param mixed $addressBookId * @param string $cardUri * @return array */ function getCard($addressBookId, $cardUri) { $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata']) ->from('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri))) ->setMaxResults(1); $result = $query->execute(); $row = $result->fetch(); if (!$row) { return false; } $row['etag'] = '"' . $row['etag'] . '"'; $row['carddata'] = $this->readBlob($row['carddata']); return $row; } /** * Returns a list of cards. * * This method should work identical to getCard, but instead return all the * cards in the list as an array. * * If the backend supports this, it may allow for some speed-ups. * * @param mixed $addressBookId * @param string[] $uris * @return array */ function getMultipleCards($addressBookId, array $uris) { if (empty($uris)) { return []; } $chunks = array_chunk($uris, 100); $cards = []; $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata']) ->from('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->andWhere($query->expr()->in('uri', $query->createParameter('uri'))); foreach ($chunks as $uris) { $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY); $result = $query->execute(); while ($row = $result->fetch()) { $row['etag'] = '"' . $row['etag'] . '"'; $row['carddata'] = $this->readBlob($row['carddata']); $cards[] = $row; } $result->closeCursor(); } return $cards; } /** * Creates a new card. * * The addressbook id will be passed as the first argument. This is the * same id as it is returned from the getAddressBooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. * * It is possible to return an ETag from this method. This ETag is for the * newly created resource, and must be enclosed with double quotes (that * is, the string itself must contain the double quotes). * * You should only return the ETag if you store the carddata as-is. If a * subsequent GET request on the same card does not have the same body, * byte-by-byte and you did return an ETag here, clients tend to get * confused. * * If you don't return an ETag, you can just return null. * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * @return string */ function createCard($addressBookId, $cardUri, $cardData) { $etag = md5($cardData); $query = $this->db->getQueryBuilder(); $query->insert('cards') ->values([ 'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB), 'uri' => $query->createNamedParameter($cardUri), 'lastmodified' => $query->createNamedParameter(time()), 'addressbookid' => $query->createNamedParameter($addressBookId), 'size' => $query->createNamedParameter(strlen($cardData)), 'etag' => $query->createNamedParameter($etag), ]) ->execute(); $this->addChange($addressBookId, $cardUri, 1); $this->updateProperties($addressBookId, $cardUri, $cardData); $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard', new GenericEvent(null, [ 'addressBookId' => $addressBookId, 'cardUri' => $cardUri, 'cardData' => $cardData])); return '"' . $etag . '"'; } /** * Updates a card. * * The addressbook id will be passed as the first argument. This is the * same id as it is returned from the getAddressBooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. * * It is possible to return an ETag from this method. This ETag should * match that of the updated resource, and must be enclosed with double * quotes (that is: the string itself must contain the actual quotes). * * You should only return the ETag if you store the carddata as-is. If a * subsequent GET request on the same card does not have the same body, * byte-by-byte and you did return an ETag here, clients tend to get * confused. * * If you don't return an ETag, you can just return null. * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * @return string */ function updateCard($addressBookId, $cardUri, $cardData) { $etag = md5($cardData); $query = $this->db->getQueryBuilder(); $query->update('cards') ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB)) ->set('lastmodified', $query->createNamedParameter(time())) ->set('size', $query->createNamedParameter(strlen($cardData))) ->set('etag', $query->createNamedParameter($etag)) ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri))) ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->execute(); $this->addChange($addressBookId, $cardUri, 2); $this->updateProperties($addressBookId, $cardUri, $cardData); $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard', new GenericEvent(null, [ 'addressBookId' => $addressBookId, 'cardUri' => $cardUri, 'cardData' => $cardData])); return '"' . $etag . '"'; } /** * Deletes a card * * @param mixed $addressBookId * @param string $cardUri * @return bool */ function deleteCard($addressBookId, $cardUri) { try { $cardId = $this->getCardId($addressBookId, $cardUri); } catch (\InvalidArgumentException $e) { $cardId = null; } $query = $this->db->getQueryBuilder(); $ret = $query->delete('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri))) ->execute(); $this->addChange($addressBookId, $cardUri, 3); $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', new GenericEvent(null, [ 'addressBookId' => $addressBookId, 'cardUri' => $cardUri])); if ($ret === 1) { if ($cardId !== null) { $this->purgeProperties($addressBookId, $cardId); } return true; } return false; } /** * The getChanges method returns all the changes that have happened, since * the specified syncToken in the specified address book. * * This function should return an array, such as the following: * * [ * 'syncToken' => 'The current synctoken', * 'added' => [ * 'new.txt', * ], * 'modified' => [ * 'modified.txt', * ], * 'deleted' => [ * 'foo.php.bak', * 'old.txt' * ] * ]; * * The returned syncToken property should reflect the *current* syncToken * of the calendar, as reported in the {http://sabredav.org/ns}sync-token * property. This is needed here too, to ensure the operation is atomic. * * If the $syncToken argument is specified as null, this is an initial * sync, and all members should be reported. * * The modified property is an array of nodenames that have changed since * the last token. * * The deleted property is an array with nodenames, that have been deleted * from collection. * * The $syncLevel argument is basically the 'depth' of the report. If it's * 1, you only have to report changes that happened only directly in * immediate descendants. If it's 2, it should also include changes from * the nodes below the child collections. (grandchildren) * * The $limit argument allows a client to specify how many results should * be returned at most. If the limit is not specified, it should be treated * as infinite. * * If the limit (infinite or not) is higher than you're willing to return, * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. * * If the syncToken is expired (due to data cleanup) or unknown, you must * return null. * * The limit is 'suggestive'. You are free to ignore it. * * @param string $addressBookId * @param string $syncToken * @param int $syncLevel * @param int $limit * @return array */ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { // Current synctoken $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?'); $stmt->execute([ $addressBookId ]); $currentToken = $stmt->fetchColumn(0); if (is_null($currentToken)) return null; $result = [ 'syncToken' => $currentToken, 'added' => [], 'modified' => [], 'deleted' => [], ]; if ($syncToken) { $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`"; if ($limit>0) { $query .= " `LIMIT` " . (int)$limit; } // Fetching all changes $stmt = $this->db->prepare($query); $stmt->execute([$syncToken, $currentToken, $addressBookId]); $changes = []; // This loop ensures that any duplicates are overwritten, only the // last change on a node is relevant. while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; } foreach($changes as $uri => $operation) { switch($operation) { case 1: $result['added'][] = $uri; break; case 2: $result['modified'][] = $uri; break; case 3: $result['deleted'][] = $uri; break; } } } else { // No synctoken supplied, this is the initial sync. $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?"; $stmt = $this->db->prepare($query); $stmt->execute([$addressBookId]); $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); } return $result; } /** * Adds a change record to the addressbookchanges table. * * @param mixed $addressBookId * @param string $objectUri * @param int $operation 1 = add, 2 = modify, 3 = delete * @return void */ protected function addChange($addressBookId, $objectUri, $operation) { $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?'; $stmt = $this->db->prepare($sql); $stmt->execute([ $objectUri, $addressBookId, $operation, $addressBookId ]); $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?'); $stmt->execute([ $addressBookId ]); } private function readBlob($cardData) { if (is_resource($cardData)) { return stream_get_contents($cardData); } return $cardData; } /** * @param IShareable $shareable * @param string[] $add * @param string[] $remove */ public function updateShares(IShareable $shareable, $add, $remove) { $this->sharingBackend->updateShares($shareable, $add, $remove); } /** * search contact * * @param int $addressBookId * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @return array an array of contacts which are arrays of key-value-pairs */ public function search($addressBookId, $pattern, $searchProperties) { $query = $this->db->getQueryBuilder(); $query2 = $this->db->getQueryBuilder(); $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp'); $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId))); $or = $query2->expr()->orX(); foreach ($searchProperties as $property) { $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property))); } $query2->andWhere($or); $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))); $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c') ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL()))); $result = $query->execute(); $cards = $result->fetchAll(); $result->closeCursor(); return array_map(function($array) { $array['carddata'] = $this->readBlob($array['carddata']); return $array; }, $cards); } /** * @param int $bookId * @param string $name * @return array */ public function collectCardProperties($bookId, $name) { $query = $this->db->getQueryBuilder(); $result = $query->selectDistinct('value') ->from($this->dbCardsPropertiesTable) ->where($query->expr()->eq('name', $query->createNamedParameter($name))) ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId))) ->execute(); $all = $result->fetchAll(PDO::FETCH_COLUMN); $result->closeCursor(); return $all; } /** * get URI from a given contact * * @param int $id * @return string */ public function getCardUri($id) { $query = $this->db->getQueryBuilder(); $query->select('uri')->from($this->dbCardsTable) ->where($query->expr()->eq('id', $query->createParameter('id'))) ->setParameter('id', $id); $result = $query->execute(); $uri = $result->fetch(); $result->closeCursor(); if (!isset($uri['uri'])) { throw new \InvalidArgumentException('Card does not exists: ' . $id); } return $uri['uri']; } /** * return contact with the given URI * * @param int $addressBookId * @param string $uri * @returns array */ public function getContact($addressBookId, $uri) { $result = []; $query = $this->db->getQueryBuilder(); $query->select('*')->from($this->dbCardsTable) ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); $queryResult = $query->execute(); $contact = $queryResult->fetch(); $queryResult->closeCursor(); if (is_array($contact)) { $result = $contact; } return $result; } /** * Returns the list of people whom this address book is shared with. * * Every element in this array should have the following properties: * * href - Often a mailto: address * * commonName - Optional, for example a first + last name * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. * * readOnly - boolean * * summary - Optional, a description for the share * * @return array */ public function getShares($addressBookId) { return $this->sharingBackend->getShares($addressBookId); } /** * update properties table * * @param int $addressBookId * @param string $cardUri * @param string $vCardSerialized */ protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) { $cardId = $this->getCardId($addressBookId, $cardUri); $vCard = $this->readCard($vCardSerialized); $this->purgeProperties($addressBookId, $cardId); $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsPropertiesTable) ->values( [ 'addressbookid' => $query->createNamedParameter($addressBookId), 'cardid' => $query->createNamedParameter($cardId), 'name' => $query->createParameter('name'), 'value' => $query->createParameter('value'), 'preferred' => $query->createParameter('preferred') ] ); foreach ($vCard->children() as $property) { if(!in_array($property->name, self::$indexProperties)) { continue; } $preferred = 0; foreach($property->parameters as $parameter) { if ($parameter->name == 'TYPE' && strtoupper($parameter->getValue()) == 'PREF') { $preferred = 1; break; } } $query->setParameter('name', $property->name); $query->setParameter('value', substr($property->getValue(), 0, 254)); $query->setParameter('preferred', $preferred); $query->execute(); } } /** * read vCard data into a vCard object * * @param string $cardData * @return VCard */ protected function readCard($cardData) { return Reader::read($cardData); } /** * delete all properties from a given card * * @param int $addressBookId * @param int $cardId */ protected function purgeProperties($addressBookId, $cardId) { $query = $this->db->getQueryBuilder(); $query->delete($this->dbCardsPropertiesTable) ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId))) ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); $query->execute(); } /** * get ID from a given contact * * @param int $addressBookId * @param string $uri * @return int */ protected function getCardId($addressBookId, $uri) { $query = $this->db->getQueryBuilder(); $query->select('id')->from($this->dbCardsTable) ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); $result = $query->execute(); $cardIds = $result->fetch(); $result->closeCursor(); if (!isset($cardIds['id'])) { throw new \InvalidArgumentException('Card does not exists: ' . $uri); } return (int)$cardIds['id']; } /** * For shared address books the sharee is set in the ACL of the address book * @param $addressBookId * @param $acl * @return array */ public function applyShareAcl($addressBookId, $acl) { return $this->sharingBackend->applyShareAcl($addressBookId, $acl); } private function convertPrincipal($principalUri, $toV2) { if ($this->principalBackend->getPrincipalPrefix() === 'principals') { list(, $name) = URLUtil::splitPath($principalUri); if ($toV2 === true) { return "principals/users/$name"; } return "principals/$name"; } return $principalUri; } private function addOwnerPrincipal(&$addressbookInfo) { $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal'; $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname'; if (isset($addressbookInfo[$ownerPrincipalKey])) { $uri = $addressbookInfo[$ownerPrincipalKey]; } else { $uri = $addressbookInfo['principaluri']; } $principalInformation = $this->principalBackend->getPrincipalByPath($uri); if (isset($principalInformation['{DAV:}displayname'])) { $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname']; } } } lib/CardDAV/AddressBookRoot.php 0000604 00000004142 15247100403 0012264 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCP\IL10N; class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot { /** @var IL10N */ protected $l10n; /** * @param \Sabre\DAVACL\PrincipalBackend\BackendInterface $principalBackend * @param \Sabre\CardDAV\Backend\BackendInterface $carddavBackend * @param string $principalPrefix */ public function __construct(\Sabre\DAVACL\PrincipalBackend\BackendInterface $principalBackend, \Sabre\CardDAV\Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals') { parent::__construct($principalBackend, $carddavBackend, $principalPrefix); $this->l10n = \OC::$server->getL10N('dav'); } /** * This method returns a node for a principal. * * The passed array contains principal information, and is guaranteed to * at least contain a uri item. Other properties may or may not be * supplied by the authentication backend. * * @param array $principal * @return \Sabre\DAV\INode */ function getChildForPrincipal(array $principal) { return new UserAddressBooks($this->carddavBackend, $principal['uri'], $this->l10n); } function getName() { if ($this->principalPrefix === 'principals') { return parent::getName(); } // Grabbing all the components of the principal path. $parts = explode('/', $this->principalPrefix); // We are only interested in the second part. return $parts[1]; } } lib/CardDAV/SyncService.php 0000604 00000022663 15247100403 0011465 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OC\Accounts\AccountManager; use OCP\AppFramework\Http; use OCP\ICertificateManager; use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; use Sabre\DAV\Client; use Sabre\DAV\Xml\Response\MultiStatus; use Sabre\DAV\Xml\Service; use Sabre\HTTP\ClientHttpException; use Sabre\VObject\Reader; class SyncService { /** @var CardDavBackend */ private $backend; /** @var IUserManager */ private $userManager; /** @var ILogger */ private $logger; /** @var array */ private $localSystemAddressBook; /** @var AccountManager */ private $accountManager; /** @var string */ protected $certPath; /** * SyncService constructor. * * @param CardDavBackend $backend * @param IUserManager $userManager * @param ILogger $logger * @param AccountManager $accountManager */ public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger, AccountManager $accountManager) { $this->backend = $backend; $this->userManager = $userManager; $this->logger = $logger; $this->accountManager = $accountManager; $this->certPath = ''; } /** * @param string $url * @param string $userName * @param string $addressBookUrl * @param string $sharedSecret * @param string $syncToken * @param int $targetBookId * @param string $targetPrincipal * @param array $targetProperties * @return string * @throws \Exception */ public function syncRemoteAddressBook($url, $userName, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetProperties) { // 1. create addressbook $book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookId, $targetProperties); $addressBookId = $book['id']; // 2. query changes try { $response = $this->requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken); } catch (ClientHttpException $ex) { if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) { // remote server revoked access to the address book, remove it $this->backend->deleteAddressBook($addressBookId); $this->logger->info('Authorization failed, remove address book: ' . $url, ['app' => 'dav']); throw $ex; } } // 3. apply changes // TODO: use multi-get for download foreach ($response['response'] as $resource => $status) { $cardUri = basename($resource); if (isset($status[200])) { $vCard = $this->download($url, $userName, $sharedSecret, $resource); $existingCard = $this->backend->getCard($addressBookId, $cardUri); if ($existingCard === false) { $this->backend->createCard($addressBookId, $cardUri, $vCard['body']); } else { $this->backend->updateCard($addressBookId, $cardUri, $vCard['body']); } } else { $this->backend->deleteCard($addressBookId, $cardUri); } } return $response['token']; } /** * @param string $principal * @param string $id * @param array $properties * @return array|null * @throws \Sabre\DAV\Exception\BadRequest */ public function ensureSystemAddressBookExists($principal, $id, $properties) { $book = $this->backend->getAddressBooksByUri($principal, $id); if (!is_null($book)) { return $book; } $this->backend->createAddressBook($principal, $id, $properties); return $this->backend->getAddressBooksByUri($principal, $id); } /** * Check if there is a valid certPath we should use * * @return string */ protected function getCertPath() { // we already have a valid certPath if ($this->certPath !== '') { return $this->certPath; } /** @var ICertificateManager $certManager */ $certManager = \OC::$server->getCertificateManager(null); $certPath = $certManager->getAbsoluteBundlePath(); if (file_exists($certPath)) { $this->certPath = $certPath; } return $this->certPath; } /** * @param string $url * @param string $userName * @param string $addressBookUrl * @param string $sharedSecret * @return Client */ protected function getClient($url, $userName, $sharedSecret) { $settings = [ 'baseUri' => $url . '/', 'userName' => $userName, 'password' => $sharedSecret, ]; $client = new Client($settings); $certPath = $this->getCertPath(); $client->setThrowExceptions(true); if ($certPath !== '' && strpos($url, 'http://') !== 0) { $client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); } return $client; } /** * @param string $url * @param string $userName * @param string $addressBookUrl * @param string $sharedSecret * @param string $syncToken * @return array */ protected function requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken) { $client = $this->getClient($url, $userName, $sharedSecret); $body = $this->buildSyncCollectionRequestBody($syncToken); $response = $client->request('REPORT', $addressBookUrl, $body, [ 'Content-Type' => 'application/xml' ]); return $this->parseMultiStatus($response['body']); } /** * @param string $url * @param string $userName * @param string $sharedSecret * @param string $resourcePath * @return array */ protected function download($url, $userName, $sharedSecret, $resourcePath) { $client = $this->getClient($url, $userName, $sharedSecret); return $client->request('GET', $resourcePath); } /** * @param string|null $syncToken * @return string */ private function buildSyncCollectionRequestBody($syncToken) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElementNS('DAV:', 'd:sync-collection'); $sync = $dom->createElement('d:sync-token', $syncToken); $prop = $dom->createElement('d:prop'); $cont = $dom->createElement('d:getcontenttype'); $etag = $dom->createElement('d:getetag'); $prop->appendChild($cont); $prop->appendChild($etag); $root->appendChild($sync); $root->appendChild($prop); $dom->appendChild($root); $body = $dom->saveXML(); return $body; } /** * @param string $body * @return array * @throws \Sabre\Xml\ParseException */ private function parseMultiStatus($body) { $xml = new Service(); /** @var MultiStatus $multiStatus */ $multiStatus = $xml->expect('{DAV:}multistatus', $body); $result = []; foreach ($multiStatus->getResponses() as $response) { $result[$response->getHref()] = $response->getResponseProperties(); } return ['response' => $result, 'token' => $multiStatus->getSyncToken()]; } /** * @param IUser $user */ public function updateUser($user) { $systemAddressBook = $this->getLocalSystemAddressBook(); $addressBookId = $systemAddressBook['id']; $converter = new Converter($this->accountManager); $name = $user->getBackendClassName(); $userId = $user->getUID(); $cardId = "$name:$userId.vcf"; $card = $this->backend->getCard($addressBookId, $cardId); if ($card === false) { $vCard = $converter->createCardFromUser($user); if ($vCard !== null) { $this->backend->createCard($addressBookId, $cardId, $vCard->serialize()); } } else { $vCard = $converter->createCardFromUser($user); if (is_null($vCard)) { $this->backend->deleteCard($addressBookId, $cardId); } else { $this->backend->updateCard($addressBookId, $cardId, $vCard->serialize()); } } } /** * @param IUser|string $userOrCardId */ public function deleteUser($userOrCardId) { $systemAddressBook = $this->getLocalSystemAddressBook(); if ($userOrCardId instanceof IUser){ $name = $userOrCardId->getBackendClassName(); $userId = $userOrCardId->getUID(); $userOrCardId = "$name:$userId.vcf"; } $this->backend->deleteCard($systemAddressBook['id'], $userOrCardId); } /** * @return array|null */ public function getLocalSystemAddressBook() { if (is_null($this->localSystemAddressBook)) { $systemPrincipal = "principals/system/system"; $this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [ '{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance' ]); } return $this->localSystemAddressBook; } public function syncInstance(\Closure $progressCallback = null) { $systemAddressBook = $this->getLocalSystemAddressBook(); $this->userManager->callForAllUsers(function($user) use ($systemAddressBook, $progressCallback) { $this->updateUser($user); if (!is_null($progressCallback)) { $progressCallback(); } }); // remove no longer existing $allCards = $this->backend->getCards($systemAddressBook['id']); foreach($allCards as $card) { $vCard = Reader::read($card['carddata']); $uid = $vCard->UID->getValue(); // load backend and see if user exists if (!$this->userManager->userExists($uid)) { $this->deleteUser($card['uri']); } } } } lib/CardDAV/PhotoCache.php 0000604 00000012105 15247100404 0011234 0 ustar 00 <?php namespace OCA\DAV\CardDAV; use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; use Sabre\CardDAV\Card; use Sabre\VObject\Property\Binary; use Sabre\VObject\Reader; class PhotoCache { /** @var IAppData $appData */ protected $appData; /** * PhotoCache constructor. * * @param IAppData $appData */ public function __construct(IAppData $appData) { $this->appData = $appData; } /** * @param int $addressBookId * @param string $cardUri * @param int $size * @param Card $card * * @return ISimpleFile * @throws NotFoundException */ public function get($addressBookId, $cardUri, $size, Card $card) { $folder = $this->getFolder($addressBookId, $cardUri); if ($this->isEmpty($folder)) { $this->init($folder, $card); } if (!$this->hasPhoto($folder)) { throw new NotFoundException(); } if ($size !== -1) { $size = 2 ** ceil(log($size) / log(2)); } return $this->getFile($folder, $size); } /** * @param ISimpleFolder $folder * @return bool */ private function isEmpty(ISimpleFolder $folder) { return $folder->getDirectoryListing() === []; } /** * @param ISimpleFolder $folder * @param Card $card */ private function init(ISimpleFolder $folder, Card $card) { $data = $this->getPhoto($card); if ($data === false) { $folder->newFile('nophoto'); } else { switch ($data['Content-Type']) { case 'image/png': $ext = 'png'; break; case 'image/jpeg': $ext = 'jpg'; break; case 'image/gif': $ext = 'gif'; break; } $file = $folder->newFile('photo.' . $ext); $file->putContent($data['body']); } } private function hasPhoto(ISimpleFolder $folder) { return !$folder->fileExists('nophoto'); } private function getFile(ISimpleFolder $folder, $size) { $ext = $this->getExtension($folder); if ($size === -1) { $path = 'photo.' . $ext; } else { $path = 'photo.' . $size . '.' . $ext; } try { $file = $folder->getFile($path); } catch (NotFoundException $e) { if ($size <= 0) { throw new NotFoundException; } $photo = new \OC_Image(); /** @var ISimpleFile $file */ $file = $folder->getFile('photo.' . $ext); $photo->loadFromData($file->getContent()); $ratio = $photo->width() / $photo->height(); if ($ratio < 1) { $ratio = 1/$ratio; } $size = (int)($size * $ratio); if ($size !== -1) { $photo->resize($size); } try { $file = $folder->newFile($path); $file->putContent($photo->data()); } catch (NotPermittedException $e) { } } return $file; } /** * @param int $addressBookId * @param string $cardUri * @return ISimpleFolder */ private function getFolder($addressBookId, $cardUri) { $hash = md5($addressBookId . ' ' . $cardUri); try { return $this->appData->getFolder($hash); } catch (NotFoundException $e) { return $this->appData->newFolder($hash); } } /** * Get the extension of the avatar. If there is no avatar throw Exception * * @param ISimpleFolder $folder * @return string * @throws NotFoundException */ private function getExtension(ISimpleFolder $folder) { if ($folder->fileExists('photo.jpg')) { return 'jpg'; } elseif ($folder->fileExists('photo.png')) { return 'png'; } elseif ($folder->fileExists('photo.gif')) { return 'gif'; } throw new NotFoundException; } private function getPhoto(Card $node) { try { $vObject = $this->readCard($node->get()); if (!$vObject->PHOTO) { return false; } $photo = $vObject->PHOTO; $type = $this->getType($photo); $val = $photo->getValue(); if ($photo->getValueType() === 'URI') { $parsed = \Sabre\URI\parse($val); //only allow data:// if ($parsed['scheme'] !== 'data') { return false; } if (substr_count($parsed['path'], ';') === 1) { list($type,) = explode(';', $parsed['path']); } $val = file_get_contents($val); } $allowedContentTypes = [ 'image/png', 'image/jpeg', 'image/gif', ]; if(!in_array($type, $allowedContentTypes, true)) { $type = 'application/octet-stream'; } return [ 'Content-Type' => $type, 'body' => $val ]; } catch(\Exception $ex) { } return false; } /** * @param string $cardData * @return \Sabre\VObject\Document */ private function readCard($cardData) { return Reader::read($cardData); } /** * @param Binary $photo * @return string */ private function getType(Binary $photo) { $params = $photo->parameters(); if (isset($params['TYPE']) || isset($params['MEDIATYPE'])) { /** @var Parameter $typeParam */ $typeParam = isset($params['TYPE']) ? $params['TYPE'] : $params['MEDIATYPE']; $type = $typeParam->getValue(); if (strpos($type, 'image/') === 0) { return $type; } else { return 'image/' . strtolower($type); } } return ''; } /** * @param int $addressBookId * @param string $cardUri */ public function delete($addressBookId, $cardUri) { $folder = $this->getFolder($addressBookId, $cardUri); $folder->delete(); } } lib/CardDAV/Plugin.php 0000604 00000004431 15247100404 0010460 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCA\DAV\CardDAV\Xml\Groups; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\HTTP\URLUtil; class Plugin extends \Sabre\CardDAV\Plugin { function initialize(Server $server) { $server->on('propFind', [$this, 'propFind']); parent::initialize($server); } /** * Returns the addressbook home for a given principal * * @param string $principal * @return string */ protected function getAddressbookHomeForPrincipal($principal) { if (strrpos($principal, 'principals/users', -strlen($principal)) !== false) { list(, $principalId) = URLUtil::splitPath($principal); return self::ADDRESSBOOK_ROOT . '/users/' . $principalId; } if (strrpos($principal, 'principals/groups', -strlen($principal)) !== false) { list(, $principalId) = URLUtil::splitPath($principal); return self::ADDRESSBOOK_ROOT . '/groups/' . $principalId; } if (strrpos($principal, 'principals/system', -strlen($principal)) !== false) { list(, $principalId) = URLUtil::splitPath($principal); return self::ADDRESSBOOK_ROOT . '/system/' . $principalId; } throw new \LogicException('This is not supposed to happen'); } /** * Adds all CardDAV-specific properties * * @param PropFind $propFind * @param INode $node * @return void */ function propFind(PropFind $propFind, INode $node) { $ns = '{http://owncloud.org/ns}'; if ($node instanceof AddressBook) { $propFind->handle($ns . 'groups', function () use ($node) { return new Groups($node->getContactsGroups()); }); } } } lib/CardDAV/ImageExportPlugin.php 0000604 00000006274 15247100404 0012634 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Georg Ehrke <georg@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCP\Files\NotFoundException; use OCP\ILogger; use Sabre\CardDAV\Card; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class ImageExportPlugin extends ServerPlugin { /** @var Server */ protected $server; /** @var PhotoCache */ private $cache; /** * ImageExportPlugin constructor. * * @param PhotoCache $cache */ public function __construct(PhotoCache $cache) { $this->cache = $cache; } /** * Initializes the plugin and registers event handlers * * @param Server $server * @return void */ public function initialize(Server $server) { $this->server = $server; $this->server->on('method:GET', [$this, 'httpGet'], 90); } /** * Intercepts GET requests on addressbook urls ending with ?photo. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ public function httpGet(RequestInterface $request, ResponseInterface $response) { $queryParams = $request->getQueryParameters(); // TODO: in addition to photo we should also add logo some point in time if (!array_key_exists('photo', $queryParams)) { return true; } $size = isset($queryParams['size']) ? (int)$queryParams['size'] : -1; $path = $request->getPath(); $node = $this->server->tree->getNodeForPath($path); if (!($node instanceof Card)) { return true; } $this->server->transactionType = 'carddav-image-export'; // Checking ACL, if available. if ($aclPlugin = $this->server->getPlugin('acl')) { /** @var \Sabre\DAVACL\Plugin $aclPlugin */ $aclPlugin->checkPrivileges($path, '{DAV:}read'); } // Fetch addressbook $addressbookpath = explode('/', $path); array_pop($addressbookpath); $addressbookpath = implode('/', $addressbookpath); /** @var AddressBook $addressbook */ $addressbook = $this->server->tree->getNodeForPath($addressbookpath); $response->setHeader('Cache-Control', 'private, max-age=3600, must-revalidate'); $response->setHeader('Etag', $node->getETag() ); $response->setHeader('Pragma', 'public'); try { $file = $this->cache->get($addressbook->getResourceId(), $node->getName(), $size, $node); $response->setHeader('Content-Type', $file->getMimeType()); $response->setHeader('Content-Disposition', 'attachment'); $response->setStatus(200); $response->setBody($file->getContent()); } catch (NotFoundException $e) { $response->setStatus(404); } return false; } } lib/CardDAV/Converter.php 0000604 00000010147 15247100404 0011172 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OC\Accounts\AccountManager; use OCP\IImage; use OCP\IUser; use Sabre\VObject\Component\VCard; use Sabre\VObject\Property\Text; class Converter { /** @var AccountManager */ private $accountManager; /** * Converter constructor. * * @param AccountManager $accountManager */ public function __construct(AccountManager $accountManager) { $this->accountManager = $accountManager; } /** * @param IUser $user * @return VCard|null */ public function createCardFromUser(IUser $user) { $userData = $this->accountManager->getUser($user); $uid = $user->getUID(); $cloudId = $user->getCloudId(); $image = $this->getAvatarImage($user); $vCard = new VCard(); $vCard->VERSION = '3.0'; $vCard->UID = $uid; $publish = false; foreach ($userData as $property => $value) { $shareWithTrustedServers = $value['scope'] === AccountManager::VISIBILITY_CONTACTS_ONLY || $value['scope'] === AccountManager::VISIBILITY_PUBLIC; $emptyValue = !isset($value['value']) || $value['value'] === ''; $noImage = $image === null; if ($shareWithTrustedServers && (!$emptyValue || !$noImage)) { $publish = true; switch ($property) { case AccountManager::PROPERTY_DISPLAYNAME: $vCard->add(new Text($vCard, 'FN', $value['value'])); $vCard->add(new Text($vCard, 'N', $this->splitFullName($value['value']))); break; case AccountManager::PROPERTY_AVATAR: if ($image !== null) { $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); } break; case AccountManager::PROPERTY_EMAIL: $vCard->add(new Text($vCard, 'EMAIL', $value['value'], ['TYPE' => 'OTHER'])); break; case AccountManager::PROPERTY_WEBSITE: $vCard->add(new Text($vCard, 'URL', $value['value'])); break; case AccountManager::PROPERTY_PHONE: $vCard->add(new Text($vCard, 'TEL', $value['value'], ['TYPE' => 'OTHER'])); break; case AccountManager::PROPERTY_ADDRESS: $vCard->add(new Text($vCard, 'ADR', $value['value'], ['TYPE' => 'OTHER'])); break; case AccountManager::PROPERTY_TWITTER: $vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $value['value'], ['TYPE' => 'TWITTER'])); break; } } } if ($publish && !empty($cloudId)) { $vCard->add(new Text($vCard, 'CLOUD', $cloudId)); $vCard->validate(); return $vCard; } return null; } /** * @param string $fullName * @return string[] */ public function splitFullName($fullName) { // Very basic western style parsing. I'm not gonna implement // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;) $elements = explode(' ', $fullName); $result = ['', '', '', '', '']; if (count($elements) > 2) { $result[0] = implode(' ', array_slice($elements, count($elements)-1)); $result[1] = $elements[0]; $result[2] = implode(' ', array_slice($elements, 1, count($elements)-2)); } elseif (count($elements) === 2) { $result[0] = $elements[1]; $result[1] = $elements[0]; } else { $result[0] = $elements[0]; } return $result; } /** * @param IUser $user * @return null|IImage */ private function getAvatarImage(IUser $user) { try { $image = $user->getAvatarImage(-1); return $image; } catch (\Exception $ex) { return null; } } } lib/CardDAV/AddressBook.php 0000604 00000013561 15247100404 0011426 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCA\DAV\DAV\Sharing\IShareable; use OCP\IL10N; use Sabre\CardDAV\Backend\BackendInterface; use Sabre\CardDAV\Card; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; /** * Class AddressBook * * @package OCA\DAV\CardDAV * @property BackendInterface|CardDavBackend $carddavBackend */ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { /** * AddressBook constructor. * * @param BackendInterface $carddavBackend * @param array $addressBookInfo * @param IL10N $l10n */ public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n) { parent::__construct($carddavBackend, $addressBookInfo); if ($this->addressBookInfo['{DAV:}displayname'] === CardDavBackend::PERSONAL_ADDRESSBOOK_NAME && $this->getName() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) { $this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Contacts'); } } /** * Updates the list of shares. * * The first array is a list of people that are to be added to the * addressbook. * * Every element in the add array has the following properties: * * href - A url. Usually a mailto: address * * commonName - Usually a first and last name, or false * * summary - A description of the share, can also be false * * readOnly - A boolean value * * Every element in the remove array is just the address string. * * @param array $add * @param array $remove * @return void * @throws Forbidden */ public function updateShares(array $add, array $remove) { if ($this->isShared()) { throw new Forbidden(); } $this->carddavBackend->updateShares($this, $add, $remove); } /** * Returns the list of people whom this addressbook is shared with. * * Every element in this array should have the following properties: * * href - Often a mailto: address * * commonName - Optional, for example a first + last name * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. * * readOnly - boolean * * summary - Optional, a description for the share * * @return array */ public function getShares() { if ($this->isShared()) { return []; } return $this->carddavBackend->getShares($this->getResourceId()); } public function getACL() { $acl = [ [ 'privilege' => '{DAV:}read', 'principal' => $this->getOwner(), 'protected' => true, ]]; $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => $this->getOwner(), 'protected' => true, ]; if ($this->getOwner() !== parent::getOwner()) { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => parent::getOwner(), 'protected' => true, ]; if ($this->canWrite()) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => parent::getOwner(), 'protected' => true, ]; } } if ($this->getOwner() === 'principals/system/system') { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => '{DAV:}authenticated', 'protected' => true, ]; } if ($this->isShared()) { return $acl; } return $this->carddavBackend->applyShareAcl($this->getResourceId(), $acl); } public function getChildACL() { return $this->getACL(); } public function getChild($name) { $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name); if (!$obj) { throw new NotFound('Card not found'); } $obj['acl'] = $this->getChildACL(); return new Card($this->carddavBackend, $this->addressBookInfo, $obj); } /** * @return int */ public function getResourceId() { return $this->addressBookInfo['id']; } public function getOwner() { if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal']; } return parent::getOwner(); } public function delete() { if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { $principal = 'principal:' . parent::getOwner(); $shares = $this->carddavBackend->getShares($this->getResourceId()); $shares = array_filter($shares, function($share) use ($principal){ return $share['href'] === $principal; }); if (empty($shares)) { throw new Forbidden(); } $this->carddavBackend->updateShares($this, [], [ 'href' => $principal ]); return; } parent::delete(); } public function propPatch(PropPatch $propPatch) { if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { throw new Forbidden(); } parent::propPatch($propPatch); } public function getContactsGroups() { return $this->carddavBackend->collectCardProperties($this->getResourceId(), 'CATEGORIES'); } private function isShared() { if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { return false; } return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'] !== $this->addressBookInfo['principaluri']; } private function canWrite() { if (isset($this->addressBookInfo['{http://owncloud.org/ns}read-only'])) { return !$this->addressBookInfo['{http://owncloud.org/ns}read-only']; } return true; } } lib/CardDAV/ContactsManager.php 0000604 00000004272 15247100404 0012276 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Georg Ehrke <georg@owncloud.com> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCP\Contacts\IManager; use OCP\IL10N; use OCP\IURLGenerator; class ContactsManager { /** @var CardDavBackend */ private $backend; /** @var IL10N */ private $l10n; /** * ContactsManager constructor. * * @param CardDavBackend $backend * @param IL10N $l10n */ public function __construct(CardDavBackend $backend, IL10N $l10n) { $this->backend = $backend; $this->l10n = $l10n; } /** * @param IManager $cm * @param string $userId * @param IURLGenerator $urlGenerator */ public function setupContactsProvider(IManager $cm, $userId, IURLGenerator $urlGenerator) { $addressBooks = $this->backend->getAddressBooksForUser("principals/users/$userId"); $this->register($cm, $addressBooks, $urlGenerator); $addressBooks = $this->backend->getAddressBooksForUser("principals/system/system"); $this->register($cm, $addressBooks, $urlGenerator); } /** * @param IManager $cm * @param $addressBooks * @param IURLGenerator $urlGenerator */ private function register(IManager $cm, $addressBooks, $urlGenerator) { foreach ($addressBooks as $addressBookInfo) { $addressBook = new \OCA\DAV\CardDAV\AddressBook($this->backend, $addressBookInfo, $this->l10n); $cm->registerAddressBook( new AddressBookImpl( $addressBook, $addressBookInfo, $this->backend, $urlGenerator ) ); } } } lib/CardDAV/SyncJob.php 0000604 00000002236 15247100404 0010572 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OC\BackgroundJob\TimedJob; use OCA\DAV\AppInfo\Application; class SyncJob extends TimedJob { public function __construct() { // Run once a day $this->setInterval(24 * 60 * 60); } protected function run($argument) { $app = new Application(); /** @var SyncService $ss */ $ss = $app->getSyncService(); $ss->syncInstance(); } } lib/CardDAV/UserAddressBooks.php 0000604 00000003716 15247100404 0012451 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCP\IL10N; class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome { /** @var IL10N */ protected $l10n; /** * Returns a list of addressbooks * * @return array */ function getChildren() { if ($this->l10n === null) { $this->l10n = \OC::$server->getL10N('dav'); } $addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); $objects = []; foreach($addressBooks as $addressBook) { $objects[] = new AddressBook($this->carddavBackend, $addressBook, $this->l10n); } return $objects; } /** * Returns a list of ACE's for this node. * * Each ACE has the following properties: * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node * * 'protected' (optional), indicating that this ACE is not allowed to * be updated. * * @return array */ function getACL() { $acl = parent::getACL(); if ($this->principalUri === 'principals/system/system') { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => '{DAV:}authenticated', 'protected' => true, ]; } return $acl; } } lib/CardDAV/Xml/Groups.php 0000604 00000002351 15247100404 0011240 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV\Xml; use Sabre\Xml\XmlSerializable; use Sabre\Xml\Writer; class Groups implements XmlSerializable { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** @var string[] of TYPE:CHECKSUM */ private $groups; /** * @param string $groups */ public function __construct($groups) { $this->groups = $groups; } function xmlSerialize(Writer $writer) { foreach ($this->groups as $group) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}group', $group); } } } lib/CardDAV/AddressBookImpl.php 0000604 00000016403 15247100404 0012246 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Georg Ehrke <georg@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CardDAV; use OCP\Constants; use OCP\IAddressBook; use OCP\IURLGenerator; use Sabre\VObject\Component\VCard; use Sabre\VObject\Property; use Sabre\VObject\Reader; use Sabre\VObject\UUIDUtil; class AddressBookImpl implements IAddressBook { /** @var CardDavBackend */ private $backend; /** @var array */ private $addressBookInfo; /** @var AddressBook */ private $addressBook; /** @var IURLGenerator */ private $urlGenerator; /** * AddressBookImpl constructor. * * @param AddressBook $addressBook * @param array $addressBookInfo * @param CardDavBackend $backend * @param IUrlGenerator $urlGenerator */ public function __construct( AddressBook $addressBook, array $addressBookInfo, CardDavBackend $backend, IURLGenerator $urlGenerator) { $this->addressBook = $addressBook; $this->addressBookInfo = $addressBookInfo; $this->backend = $backend; $this->urlGenerator = $urlGenerator; } /** * @return string defining the technical unique key * @since 5.0.0 */ public function getKey() { return $this->addressBookInfo['id']; } /** * In comparison to getKey() this function returns a human readable (maybe translated) name * * @return mixed * @since 5.0.0 */ public function getDisplayName() { return $this->addressBookInfo['{DAV:}displayname']; } /** * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options - for future use. One should always have options! * @return array an array of contacts which are arrays of key-value-pairs * @since 5.0.0 */ public function search($pattern, $searchProperties, $options) { $results = $this->backend->search($this->getKey(), $pattern, $searchProperties); $vCards = []; foreach ($results as $result) { $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata'])); } return $vCards; } /** * @param array $properties this array if key-value-pairs defines a contact * @return array an array representing the contact just created or updated * @since 5.0.0 */ public function createOrUpdate($properties) { $update = false; if (!isset($properties['URI'])) { // create a new contact $uid = $this->createUid(); $uri = $uid . '.vcf'; $vCard = $this->createEmptyVCard($uid); } else { // update existing contact $uri = $properties['URI']; $vCardData = $this->backend->getCard($this->getKey(), $uri); $vCard = $this->readCard($vCardData['carddata']); $update = true; } foreach ($properties as $key => $value) { $vCard->$key = $vCard->createProperty($key, $value); } if ($update) { $this->backend->updateCard($this->getKey(), $uri, $vCard->serialize()); } else { $this->backend->createCard($this->getKey(), $uri, $vCard->serialize()); } return $this->vCard2Array($uri, $vCard); } /** * @return mixed * @since 5.0.0 */ public function getPermissions() { $permissions = $this->addressBook->getACL(); $result = 0; foreach ($permissions as $permission) { switch($permission['privilege']) { case '{DAV:}read': $result |= Constants::PERMISSION_READ; break; case '{DAV:}write': $result |= Constants::PERMISSION_CREATE; $result |= Constants::PERMISSION_UPDATE; break; case '{DAV:}all': $result |= Constants::PERMISSION_ALL; break; } } return $result; } /** * @param object $id the unique identifier to a contact * @return bool successful or not * @since 5.0.0 */ public function delete($id) { $uri = $this->backend->getCardUri($id); return $this->backend->deleteCard($this->addressBookInfo['id'], $uri); } /** * read vCard data into a vCard object * * @param string $cardData * @return VCard */ protected function readCard($cardData) { return Reader::read($cardData); } /** * create UID for contact * * @return string */ protected function createUid() { do { $uid = $this->getUid(); $contact = $this->backend->getContact($this->getKey(), $uid . '.vcf'); } while (!empty($contact)); return $uid; } /** * getUid is only there for testing, use createUid instead */ protected function getUid() { return UUIDUtil::getUUID(); } /** * create empty vcard * * @param string $uid * @return VCard */ protected function createEmptyVCard($uid) { $vCard = new VCard(); $vCard->UID = $uid; return $vCard; } /** * create array with all vCard properties * * @param string $uri * @param VCard $vCard * @return array */ protected function vCard2Array($uri, VCard $vCard) { $result = [ 'URI' => $uri, ]; foreach ($vCard->children() as $property) { if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') { $url = $this->urlGenerator->getAbsoluteURL( $this->urlGenerator->linkTo('', 'remote.php') . '/dav/'); $url .= implode('/', [ 'addressbooks', substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/' $this->addressBookInfo['uri'], $uri ]) . '?photo'; $result['PHOTO'] = 'VALUE=uri:' . $url; } else if ($property->name === 'X-SOCIALPROFILE') { $type = $this->getTypeFromProperty($property); // Type is the social network, when it's empty we don't need this. if ($type !== null) { if (!isset($result[$property->name])) { $result[$property->name] = []; } $result[$property->name][$type] = $property->getValue(); } // The following properties can be set multiple times } else if (in_array($property->name, ['CLOUD', 'EMAIL', 'IMPP', 'TEL', 'URL'])) { if (!isset($result[$property->name])) { $result[$property->name] = []; } $result[$property->name][] = $property->getValue(); } else { $result[$property->name] = $property->getValue(); } } if ($this->addressBookInfo['principaluri'] === 'principals/system/system' && $this->addressBookInfo['uri'] === 'system') { $result['isLocalSystemBook'] = true; } return $result; } /** * Get the type of the current property * * @param Property $property * @return null|string */ protected function getTypeFromProperty(Property $property) { $parameters = $property->parameters(); // Type is the social network, when it's empty we don't need this. if (isset($parameters['TYPE'])) { /** @var \Sabre\VObject\Parameter $type */ $type = $parameters['TYPE']; return $type->getValue(); } return null; } } lib/Command/CreateCalendar.php 0000604 00000005175 15247100404 0012237 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Command; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Connector\Sabre\Principal; use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IUserManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class CreateCalendar extends Command { /** @var IUserManager */ protected $userManager; /** @var IGroupManager $groupManager */ private $groupManager; /** @var \OCP\IDBConnection */ protected $dbConnection; /** * @param IUserManager $userManager * @param IGroupManager $groupManager * @param IDBConnection $dbConnection */ function __construct(IUserManager $userManager, IGroupManager $groupManager, IDBConnection $dbConnection) { parent::__construct(); $this->userManager = $userManager; $this->groupManager = $groupManager; $this->dbConnection = $dbConnection; } protected function configure() { $this ->setName('dav:create-calendar') ->setDescription('Create a dav calendar') ->addArgument('user', InputArgument::REQUIRED, 'User for whom the calendar will be created') ->addArgument('name', InputArgument::REQUIRED, 'Name of the calendar'); } protected function execute(InputInterface $input, OutputInterface $output) { $user = $input->getArgument('user'); if (!$this->userManager->userExists($user)) { throw new \InvalidArgumentException("User <$user> in unknown."); } $principalBackend = new Principal( $this->userManager, $this->groupManager ); $random = \OC::$server->getSecureRandom(); $dispatcher = \OC::$server->getEventDispatcher(); $name = $input->getArgument('name'); $caldav = new CalDavBackend($this->dbConnection, $principalBackend, $this->userManager, $random, $dispatcher); $caldav->createCalendar("principals/users/$user", $name, []); } } lib/Command/SyncBirthdayCalendar.php 0000604 00000005154 15247100404 0013434 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Command; use OCA\DAV\CalDAV\BirthdayService; use OCP\IUser; use OCP\IUserManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class SyncBirthdayCalendar extends Command { /** @var BirthdayService */ private $birthdayService; /** @var IUserManager */ private $userManager; /** * @param IUserManager $userManager * @param BirthdayService $birthdayService */ function __construct(IUserManager $userManager, BirthdayService $birthdayService) { parent::__construct(); $this->birthdayService = $birthdayService; $this->userManager = $userManager; } protected function configure() { $this ->setName('dav:sync-birthday-calendar') ->setDescription('Synchronizes the birthday calendar') ->addArgument('user', InputArgument::OPTIONAL, 'User for whom the birthday calendar will be synchronized'); } /** * @param InputInterface $input * @param OutputInterface $output */ protected function execute(InputInterface $input, OutputInterface $output) { $user = $input->getArgument('user'); if (!is_null($user)) { if (!$this->userManager->userExists($user)) { throw new \InvalidArgumentException("User <$user> in unknown."); } $output->writeln("Start birthday calendar sync for $user"); $this->birthdayService->syncUser($user); return; } $output->writeln("Start birthday calendar sync for all users ..."); $p = new ProgressBar($output); $p->start(); $this->userManager->callForAllUsers(function($user) use ($p) { $p->advance(); /** @var IUser $user */ $this->birthdayService->syncUser($user->getUID()); }); $p->finish(); $output->writeln(''); } } lib/Command/CreateAddressBook.php 0000604 00000004277 15247100404 0012730 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Command; use OCA\DAV\CardDAV\CardDavBackend; use OCP\IUserManager; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class CreateAddressBook extends Command { /** @var IUserManager */ private $userManager; /** @var CardDavBackend */ private $cardDavBackend; /** * @param IUserManager $userManager * @param CardDavBackend $cardDavBackend */ function __construct(IUserManager $userManager, CardDavBackend $cardDavBackend ) { parent::__construct(); $this->userManager = $userManager; $this->cardDavBackend = $cardDavBackend; } protected function configure() { $this ->setName('dav:create-addressbook') ->setDescription('Create a dav addressbook') ->addArgument('user', InputArgument::REQUIRED, 'User for whom the addressbook will be created') ->addArgument('name', InputArgument::REQUIRED, 'Name of the addressbook'); } protected function execute(InputInterface $input, OutputInterface $output) { $user = $input->getArgument('user'); if (!$this->userManager->userExists($user)) { throw new \InvalidArgumentException("User <$user> in unknown."); } $name = $input->getArgument('name'); $this->cardDavBackend->createAddressBook("principals/users/$user", $name, []); } } lib/Command/SyncSystemAddressBook.php 0000604 00000003467 15247100404 0013646 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Command; use OCA\DAV\CardDAV\SyncService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class SyncSystemAddressBook extends Command { /** @var SyncService */ private $syncService; /** * @param SyncService $syncService */ function __construct(SyncService $syncService) { parent::__construct(); $this->syncService = $syncService; } protected function configure() { $this ->setName('dav:sync-system-addressbook') ->setDescription('Synchronizes users to the system addressbook'); } /** * @param InputInterface $input * @param OutputInterface $output */ protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('Syncing users ...'); $progress = new ProgressBar($output); $progress->start(); $this->syncService->syncInstance(function() use ($progress) { $progress->advance(); }); $progress->finish(); $output->writeln(''); } } lib/Files/BrowserErrorPagePlugin.php 0000604 00000005453 15247100404 0013476 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Files; use OC\AppFramework\Http\Request; use OC_Template; use OCP\IRequest; use Sabre\DAV\Exception; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; class BrowserErrorPagePlugin extends ServerPlugin { /** @var Server */ private $server; /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server * @return void */ function initialize(Server $server) { $this->server = $server; $server->on('exception', array($this, 'logException'), 1000); } /** * @param IRequest $request * @return bool */ public static function isBrowserRequest(IRequest $request) { if ($request->getMethod() !== 'GET') { return false; } return $request->isUserAgent([ Request::USER_AGENT_IE, Request::USER_AGENT_MS_EDGE, Request::USER_AGENT_CHROME, Request::USER_AGENT_FIREFOX, Request::USER_AGENT_SAFARI, ]); } /** * @param \Exception $ex */ public function logException(\Exception $ex) { if ($ex instanceof Exception) { $httpCode = $ex->getHTTPCode(); $headers = $ex->getHTTPHeaders($this->server); } else { $httpCode = 500; $headers = []; } $this->server->httpResponse->addHeaders($headers); $this->server->httpResponse->setStatus($httpCode); $body = $this->generateBody(); $this->server->httpResponse->setBody($body); $this->sendResponse(); } /** * @codeCoverageIgnore * @return bool|string */ public function generateBody() { $request = \OC::$server->getRequest(); $content = new OC_Template('dav', 'exception', 'guest'); $content->assign('title', $this->server->httpResponse->getStatusText()); $content->assign('remoteAddr', $request->getRemoteAddress()); $content->assign('requestID', $request->getId()); return $content->fetchPage(); } /** * @codeCoverageIgnore */ public function sendResponse() { $this->server->sapi->sendResponse($this->server->httpResponse); exit(); } } lib/Files/FilesHome.php 0000604 00000003211 15247100404 0010726 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Files; use OCA\DAV\Connector\Sabre\Directory; use OCP\Files\FileInfo; use Sabre\DAV\Exception\Forbidden; use Sabre\HTTP\URLUtil; class FilesHome extends Directory { /** * @var array */ private $principalInfo; /** * FilesHome constructor. * * @param array $principalInfo */ public function __construct($principalInfo) { $this->principalInfo = $principalInfo; $view = \OC\Files\Filesystem::getView(); $rootInfo = $view->getFileInfo(''); if (!($rootInfo instanceof FileInfo)) { throw new \Exception('Home does not exist'); } parent::__construct($view, $rootInfo); } function delete() { throw new Forbidden('Permission denied to delete home folder'); } function getName() { list(,$name) = URLUtil::splitPath($this->principalInfo['uri']); return $name; } function setName($name) { throw new Forbidden('Permission denied to rename this folder'); } } lib/Files/RootCollection.php 0000604 00000003505 15247100404 0012020 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Files; use Sabre\DAV\INode; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\HTTP\URLUtil; use Sabre\DAV\SimpleCollection; class RootCollection extends AbstractPrincipalCollection { /** * This method returns a node for a principal. * * The passed array contains principal information, and is guaranteed to * at least contain a uri item. Other properties may or may not be * supplied by the authentication backend. * * @param array $principalInfo * @return INode */ function getChildForPrincipal(array $principalInfo) { list(,$name) = URLUtil::splitPath($principalInfo['uri']); $user = \OC::$server->getUserSession()->getUser(); if (is_null($user) || $name !== $user->getUID()) { // a user is only allowed to see their own home contents, so in case another collection // is accessed, we return a simple empty collection for now // in the future this could be considered to be used for accessing shared files return new SimpleCollection($name); } return new FilesHome($principalInfo); } function getName() { return 'files'; } } lib/Files/Sharing/FilesDropPlugin.php 0000604 00000004165 15247100404 0013525 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Files\Sharing; use OC\Files\View; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; /** * Make sure that the destination is writable */ class FilesDropPlugin extends ServerPlugin { /** @var View */ private $view; /** @var bool */ private $enabled = false; /** * @param View $view */ public function setView($view) { $this->view = $view; } public function enable() { $this->enabled = true; } /** * This initializes the plugin. * * @param \Sabre\DAV\Server $server Sabre server * * @return void * @throws MethodNotAllowed */ public function initialize(\Sabre\DAV\Server $server) { $server->on('beforeMethod', [$this, 'beforeMethod'], 999); $this->enabled = false; } public function beforeMethod(RequestInterface $request, ResponseInterface $response){ if (!$this->enabled) { return; } if ($request->getMethod() !== 'PUT') { throw new MethodNotAllowed('Only PUT is allowed on files drop'); } $path = explode('/', $request->getPath()); $path = array_pop($path); $newName = \OC_Helper::buildNotExistingFileNameForView('/', $path, $this->view); $url = $request->getBaseUrl() . $newName; $request->setUrl($url); } } lib/Files/Sharing/PublicLinkCheckPlugin.php 0000604 00000003241 15247100404 0014622 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Robin Appelman <robin@icewind.nl> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Files\Sharing; use OCP\Files\FileInfo; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; /** * Verify that the public link share is valid */ class PublicLinkCheckPlugin extends ServerPlugin { /** * @var FileInfo */ private $fileInfo; /** * @param FileInfo $fileInfo */ public function setFileInfo($fileInfo) { $this->fileInfo = $fileInfo; } /** * This initializes the plugin. * * @param \Sabre\DAV\Server $server Sabre server * * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->on('beforeMethod', [$this, 'beforeMethod']); } public function beforeMethod(RequestInterface $request, ResponseInterface $response){ // verify that the owner didn't have his share permissions revoked if ($this->fileInfo && !$this->fileInfo->isShareable()) { throw new NotFound(); } } } lib/Files/FileSearchBackend.php 0000604 00000023460 15247100404 0012340 0 ustar 00 <?php /** * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Files; use OC\Files\Search\SearchBinaryOperator; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\TagsPlugin; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\Search\ISearchOperator; use OCP\Files\Search\ISearchOrder; use OCP\Files\Search\ISearchQuery; use OCP\IUser; use OCP\Share\IManager; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Tree; use SearchDAV\Backend\ISearchBackend; use SearchDAV\Backend\SearchPropertyDefinition; use SearchDAV\Backend\SearchResult; use SearchDAV\XML\BasicSearch; use SearchDAV\XML\Literal; use SearchDAV\XML\Operator; use SearchDAV\XML\Order; class FileSearchBackend implements ISearchBackend { /** @var Tree */ private $tree; /** @var IUser */ private $user; /** @var IRootFolder */ private $rootFolder; /** @var IManager */ private $shareManager; /** @var View */ private $view; /** * FileSearchBackend constructor. * * @param Tree $tree * @param IUser $user * @param IRootFolder $rootFolder * @param IManager $shareManager * @param View $view * @internal param IRootFolder $rootFolder */ public function __construct(Tree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) { $this->tree = $tree; $this->user = $user; $this->rootFolder = $rootFolder; $this->shareManager = $shareManager; $this->view = $view; } /** * Search endpoint will be remote.php/dav * * @return string */ public function getArbiterPath() { return ''; } public function isValidScope($href, $depth, $path) { // only allow scopes inside the dav server if (is_null($path)) { return false; } try { $node = $this->tree->getNodeForPath($path); return $node instanceof Directory; } catch (NotFound $e) { return false; } } public function getPropertyDefinitionsForScope($href, $path) { // all valid scopes support the same schema //todo dynamically load all propfind properties that are supported return [ // queryable properties new SearchPropertyDefinition('{DAV:}displayname', true, false, true), new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME), new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN), new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), // select only properties new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false), new SearchPropertyDefinition('{DAV:}getcontentlength', false, true, false), new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false), new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false), new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false), new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, false, true, false), new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false), new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false), new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN), new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER), ]; } /** * @param BasicSearch $search * @return SearchResult[] */ public function search(BasicSearch $search) { if (count($search->from) !== 1) { throw new \InvalidArgumentException('Searching more than one folder is not supported'); } $query = $this->transformQuery($search); $scope = $search->from[0]; if ($scope->path === null) { throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead'); } $node = $this->tree->getNodeForPath($scope->path); if (!$node instanceof Directory) { throw new \InvalidArgumentException('Search is only supported on directories'); } $fileInfo = $node->getFileInfo(); $folder = $this->rootFolder->get($fileInfo->getPath()); /** @var Folder $folder $results */ $results = $folder->search($query); return array_map(function (Node $node) { if ($node instanceof Folder) { return new SearchResult(new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager), $this->getHrefForNode($node)); } else { return new SearchResult(new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager), $this->getHrefForNode($node)); } }, $results); } /** * @param Node $node * @return string */ private function getHrefForNode(Node $node) { $base = '/files/' . $this->user->getUID(); return $base . $this->view->getRelativePath($node->getPath()); } /** * @param BasicSearch $query * @return ISearchQuery */ private function transformQuery(BasicSearch $query) { // TODO offset, limit $orders = array_map([$this, 'mapSearchOrder'], $query->orderBy); return new SearchQuery($this->transformSearchOperation($query->where), 0, 0, $orders, $this->user); } /** * @param Order $order * @return ISearchOrder */ private function mapSearchOrder(Order $order) { return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property)); } /** * @param Operator $operator * @return ISearchOperator */ private function transformSearchOperation(Operator $operator) { list(, $trimmedType) = explode('}', $operator->type); switch ($operator->type) { case Operator::OPERATION_AND: case Operator::OPERATION_OR: case Operator::OPERATION_NOT: $arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments); return new SearchBinaryOperator($trimmedType, $arguments); case Operator::OPERATION_EQUAL: case Operator::OPERATION_GREATER_OR_EQUAL_THAN: case Operator::OPERATION_GREATER_THAN: case Operator::OPERATION_LESS_OR_EQUAL_THAN: case Operator::OPERATION_LESS_THAN: case Operator::OPERATION_IS_LIKE: if (count($operator->arguments) !== 2) { throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation'); } if (!is_string($operator->arguments[0])) { throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property'); } if (!($operator->arguments[1] instanceof Literal)) { throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal'); } return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value)); case Operator::OPERATION_IS_COLLECTION: return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE); default: throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType . ' (' . $operator->type . ')'); } } /** * @param string $propertyName * @return string */ private function mapPropertyNameToColumn($propertyName) { switch ($propertyName) { case '{DAV:}displayname': return 'name'; case '{DAV:}getcontenttype': return 'mimetype'; case '{DAV:}getlastmodified': return 'mtime'; case FilesPlugin::SIZE_PROPERTYNAME: return 'size'; case TagsPlugin::FAVORITE_PROPERTYNAME: return 'favorite'; case TagsPlugin::TAGS_PROPERTYNAME: return 'tagname'; case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME: return 'fileid'; default: throw new \InvalidArgumentException('Unsupported property for search or order: ' . $propertyName); } } private function castValue($propertyName, $value) { $allProps = $this->getPropertyDefinitionsForScope('', ''); foreach ($allProps as $prop) { if ($prop->name === $propertyName) { $dataType = $prop->dataType; switch ($dataType) { case SearchPropertyDefinition::DATATYPE_BOOLEAN: return $value === 'yes'; case SearchPropertyDefinition::DATATYPE_DECIMAL: case SearchPropertyDefinition::DATATYPE_INTEGER: case SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER: return 0 + $value; case SearchPropertyDefinition::DATATYPE_DATETIME: if (is_numeric($value)) { return 0 + $value; } $date = \DateTime::createFromFormat(\DateTime::ATOM, $value); return ($date instanceof \DateTime) ? $date->getTimestamp() : 0; default: return $value; } } } return $value; } } lib/SystemTag/SystemTagsObjectTypeCollection.php 0000604 00000007562 15247100404 0016056 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\IUserSession; use OCP\IGroupManager; /** * Collection containing object ids by object type */ class SystemTagsObjectTypeCollection implements ICollection { /** * @var string */ private $objectType; /** * @var ISystemTagManager */ private $tagManager; /** * @var ISystemTagObjectMapper */ private $tagMapper; /** * @var IGroupManager */ private $groupManager; /** * @var IUserSession */ private $userSession; /** * @var \Closure **/ protected $childExistsFunction; /** * Constructor * * @param string $objectType object type * @param ISystemTagManager $tagManager * @param ISystemTagObjectMapper $tagMapper * @param IUserSession $userSession * @param IGroupManager $groupManager * @param \Closure $childExistsFunction */ public function __construct( $objectType, ISystemTagManager $tagManager, ISystemTagObjectMapper $tagMapper, IUserSession $userSession, IGroupManager $groupManager, \Closure $childExistsFunction ) { $this->tagManager = $tagManager; $this->tagMapper = $tagMapper; $this->objectType = $objectType; $this->userSession = $userSession; $this->groupManager = $groupManager; $this->childExistsFunction = $childExistsFunction; } /** * @param string $name * @param resource|string $data Initial payload * @return null|string * @throws Forbidden */ function createFile($name, $data = null) { throw new Forbidden('Permission denied to create nodes'); } /** * @param string $name * @throws Forbidden */ function createDirectory($name) { throw new Forbidden('Permission denied to create collections'); } /** * @param string $objectId * @return SystemTagsObjectMappingCollection * @throws NotFound */ function getChild($objectId) { // make sure the object exists and is reachable if(!$this->childExists($objectId)) { throw new NotFound('Entity does not exist or is not available'); } return new SystemTagsObjectMappingCollection( $objectId, $this->objectType, $this->userSession->getUser(), $this->tagManager, $this->tagMapper ); } function getChildren() { // do not list object ids throw new MethodNotAllowed(); } /** * Checks if a child-node with the specified name exists * * @param string $name * @return bool */ function childExists($name) { return call_user_func($this->childExistsFunction, $name); } function delete() { throw new Forbidden('Permission denied to delete this collection'); } function getName() { return $this->objectType; } /** * @param string $name * @throws Forbidden */ function setName($name) { throw new Forbidden('Permission denied to rename this collection'); } /** * Returns the last modification time, as a unix timestamp * * @return int */ function getLastModified() { return null; } } lib/SystemTag/SystemTagsRelationsCollection.php 0000604 00000005141 15247100404 0015735 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\SystemTagsEntityEvent; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\SimpleCollection; use OCP\IUserSession; use OCP\IGroupManager; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class SystemTagsRelationsCollection extends SimpleCollection { /** * SystemTagsRelationsCollection constructor. * * @param ISystemTagManager $tagManager * @param ISystemTagObjectMapper $tagMapper * @param IUserSession $userSession * @param IGroupManager $groupManager * @param EventDispatcherInterface $dispatcher */ public function __construct( ISystemTagManager $tagManager, ISystemTagObjectMapper $tagMapper, IUserSession $userSession, IGroupManager $groupManager, EventDispatcherInterface $dispatcher ) { $children = [ new SystemTagsObjectTypeCollection( 'files', $tagManager, $tagMapper, $userSession, $groupManager, function($name) { $nodes = \OC::$server->getUserFolder()->getById(intval($name)); return !empty($nodes); } ), ]; $event = new SystemTagsEntityEvent(SystemTagsEntityEvent::EVENT_ENTITY); $dispatcher->dispatch(SystemTagsEntityEvent::EVENT_ENTITY, $event); foreach ($event->getEntityCollections() as $entity => $entityExistsFunction) { $children[] = new SystemTagsObjectTypeCollection( $entity, $tagManager, $tagMapper, $userSession, $groupManager, $entityExistsFunction ); } parent::__construct('root', $children); } function getName() { return 'systemtags-relations'; } function setName($name) { throw new Forbidden('Permission denied to rename this collection'); } } lib/SystemTag/SystemTagNode.php 0000604 00000011062 15247100404 0012462 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\Conflict; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\TagNotFoundException; use OCP\SystemTag\TagAlreadyExistsException; use OCP\IUser; /** * DAV node representing a system tag, with the name being the tag id. */ class SystemTagNode implements \Sabre\DAV\INode { /** * @var ISystemTag */ protected $tag; /** * @var ISystemTagManager */ protected $tagManager; /** * User * * @var IUser */ protected $user; /** * Whether to allow permissions for admins * * @var bool */ protected $isAdmin; /** * Sets up the node, expects a full path name * * @param ISystemTag $tag system tag * @param IUser $user user * @param bool $isAdmin whether to allow operations for admins * @param ISystemTagManager $tagManager tag manager */ public function __construct(ISystemTag $tag, IUser $user, $isAdmin, ISystemTagManager $tagManager) { $this->tag = $tag; $this->user = $user; $this->isAdmin = $isAdmin; $this->tagManager = $tagManager; } /** * Returns the id of the tag * * @return string */ public function getName() { return $this->tag->getId(); } /** * Returns the system tag represented by this node * * @return ISystemTag system tag */ public function getSystemTag() { return $this->tag; } /** * Renames the node * * @param string $name The new name * * @throws MethodNotAllowed not allowed to rename node */ public function setName($name) { throw new MethodNotAllowed(); } /** * Update tag * * @param string $name new tag name * @param bool $userVisible user visible * @param bool $userAssignable user assignable * @throws NotFound whenever the given tag id does not exist * @throws Forbidden whenever there is no permission to update said tag * @throws Conflict whenever a tag already exists with the given attributes */ public function update($name, $userVisible, $userAssignable) { try { if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) { throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist'); } if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) { throw new Forbidden('No permission to update tag ' . $this->tag->getId()); } // only admin is able to change permissions, regular users can only rename if (!$this->isAdmin) { // only renaming is allowed for regular users if ($userVisible !== $this->tag->isUserVisible() || $userAssignable !== $this->tag->isUserAssignable() ) { throw new Forbidden('No permission to update permissions for tag ' . $this->tag->getId()); } } $this->tagManager->updateTag($this->tag->getId(), $name, $userVisible, $userAssignable); } catch (TagNotFoundException $e) { throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist'); } catch (TagAlreadyExistsException $e) { throw new Conflict( 'Tag with the properties "' . $name . '", ' . $userVisible . ', ' . $userAssignable . ' already exists' ); } } /** * Returns null, not supported * */ public function getLastModified() { return null; } public function delete() { try { if (!$this->isAdmin) { throw new Forbidden('No permission to delete tag ' . $this->tag->getId()); } if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) { throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found'); } $this->tagManager->deleteTags($this->tag->getId()); } catch (TagNotFoundException $e) { // can happen if concurrent deletion occurred throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e); } } } lib/SystemTag/SystemTagPlugin.php 0000604 00000022347 15247100404 0013043 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use OCP\IGroupManager; use OCP\IUserSession; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Conflict; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\UnsupportedMediaType; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\TagAlreadyExistsException; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; /** * Sabre plugin to handle system tags: * * - makes it possible to create new tags with POST operation * - get/set Webdav properties for tags * */ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const ID_PROPERTYNAME = '{http://owncloud.org/ns}id'; const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name'; const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible'; const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable'; const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups'; const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign'; /** * @var \Sabre\DAV\Server $server */ private $server; /** * @var ISystemTagManager */ protected $tagManager; /** * @var IUserSession */ protected $userSession; /** * @var IGroupManager */ protected $groupManager; /** * @param ISystemTagManager $tagManager tag manager * @param IGroupManager $groupManager * @param IUserSession $userSession */ public function __construct(ISystemTagManager $tagManager, IGroupManager $groupManager, IUserSession $userSession) { $this->tagManager = $tagManager; $this->userSession = $userSession; $this->groupManager = $groupManager; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $server->protectedProperties[] = self::ID_PROPERTYNAME; $server->on('propFind', array($this, 'handleGetProperties')); $server->on('propPatch', array($this, 'handleUpdateProperties')); $server->on('method:POST', [$this, 'httpPost']); $this->server = $server; } /** * POST operation on system tag collections * * @param RequestInterface $request request object * @param ResponseInterface $response response object * @return null|false */ public function httpPost(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); // Making sure the node exists $node = $this->server->tree->getNodeForPath($path); if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) { $data = $request->getBodyAsString(); $tag = $this->createTag($data, $request->getHeader('Content-Type')); if ($node instanceof SystemTagsObjectMappingCollection) { // also add to collection $node->createFile($tag->getId()); $url = $request->getBaseUrl() . 'systemtags/'; } else { $url = $request->getUrl(); } if ($url[strlen($url) - 1] !== '/') { $url .= '/'; } $response->setHeader('Content-Location', $url . $tag->getId()); // created $response->setStatus(201); return false; } } /** * Creates a new tag * * @param string $data JSON encoded string containing the properties of the tag to create * @param string $contentType content type of the data * @return ISystemTag newly created system tag * * @throws BadRequest if a field was missing * @throws Conflict if a tag with the same properties already exists * @throws UnsupportedMediaType if the content type is not supported */ private function createTag($data, $contentType = 'application/json') { if (explode(';', $contentType)[0] === 'application/json') { $data = json_decode($data, true); } else { throw new UnsupportedMediaType(); } if (!isset($data['name'])) { throw new BadRequest('Missing "name" attribute'); } $tagName = $data['name']; $userVisible = true; $userAssignable = true; if (isset($data['userVisible'])) { $userVisible = (bool)$data['userVisible']; } if (isset($data['userAssignable'])) { $userAssignable = (bool)$data['userAssignable']; } $groups = []; if (isset($data['groups'])) { $groups = $data['groups']; if (is_string($groups)) { $groups = explode('|', $groups); } } if($userVisible === false || $userAssignable === false || !empty($groups)) { if(!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) { throw new BadRequest('Not sufficient permissions'); } } try { $tag = $this->tagManager->createTag($tagName, $userVisible, $userAssignable); if (!empty($groups)) { $this->tagManager->setTagGroups($tag, $groups); } return $tag; } catch (TagAlreadyExistsException $e) { throw new Conflict('Tag already exists', 0, $e); } } /** * Retrieves system tag properties * * @param PropFind $propFind * @param \Sabre\DAV\INode $node */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $node ) { if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) { return; } $propFind->handle(self::ID_PROPERTYNAME, function() use ($node) { return $node->getSystemTag()->getId(); }); $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function() use ($node) { return $node->getSystemTag()->getName(); }); $propFind->handle(self::USERVISIBLE_PROPERTYNAME, function() use ($node) { return $node->getSystemTag()->isUserVisible() ? 'true' : 'false'; }); $propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function() use ($node) { // this is the tag's inherent property "is user assignable" return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false'; }); $propFind->handle(self::CANASSIGN_PROPERTYNAME, function() use ($node) { // this is the effective permission for the current user return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false'; }); $propFind->handle(self::GROUPS_PROPERTYNAME, function() use ($node) { if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) { // property only available for admins throw new Forbidden(); } $groups = []; // no need to retrieve groups for namespaces that don't qualify if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) { $groups = $this->tagManager->getTagGroups($node->getSystemTag()); } return implode('|', $groups); }); } /** * Updates tag attributes * * @param string $path * @param PropPatch $propPatch * * @return void */ public function handleUpdateProperties($path, PropPatch $propPatch) { $node = $this->server->tree->getNodeForPath($path); if (!($node instanceof SystemTagNode)) { return; } $propPatch->handle([ self::DISPLAYNAME_PROPERTYNAME, self::USERVISIBLE_PROPERTYNAME, self::USERASSIGNABLE_PROPERTYNAME, self::GROUPS_PROPERTYNAME, ], function($props) use ($node) { $tag = $node->getSystemTag(); $name = $tag->getName(); $userVisible = $tag->isUserVisible(); $userAssignable = $tag->isUserAssignable(); $updateTag = false; if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) { $name = $props[self::DISPLAYNAME_PROPERTYNAME]; $updateTag = true; } if (isset($props[self::USERVISIBLE_PROPERTYNAME])) { $propValue = $props[self::USERVISIBLE_PROPERTYNAME]; $userVisible = ($propValue !== 'false' && $propValue !== '0'); $updateTag = true; } if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) { $propValue = $props[self::USERASSIGNABLE_PROPERTYNAME]; $userAssignable = ($propValue !== 'false' && $propValue !== '0'); $updateTag = true; } if (isset($props[self::GROUPS_PROPERTYNAME])) { if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) { // property only available for admins throw new Forbidden(); } $propValue = $props[self::GROUPS_PROPERTYNAME]; $groupIds = explode('|', $propValue); $this->tagManager->setTagGroups($tag, $groupIds); } if ($updateTag) { $node->update($name, $userVisible, $userAssignable); } return true; }); } } lib/SystemTag/SystemTagsObjectMappingCollection.php 0000604 00000012077 15247100404 0016525 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\ICollection; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\TagNotFoundException; use OCP\IUser; /** * Collection containing tags by object id */ class SystemTagsObjectMappingCollection implements ICollection { /** * @var string */ private $objectId; /** * @var string */ private $objectType; /** * @var ISystemTagManager */ private $tagManager; /** * @var ISystemTagObjectMapper */ private $tagMapper; /** * User * * @var IUser */ private $user; /** * Constructor * * @param string $objectId object id * @param string $objectType object type * @param IUser $user user * @param ISystemTagManager $tagManager tag manager * @param ISystemTagObjectMapper $tagMapper tag mapper */ public function __construct( $objectId, $objectType, IUser $user, ISystemTagManager $tagManager, ISystemTagObjectMapper $tagMapper ) { $this->tagManager = $tagManager; $this->tagMapper = $tagMapper; $this->objectId = $objectId; $this->objectType = $objectType; $this->user = $user; } function createFile($tagId, $data = null) { try { $tags = $this->tagManager->getTagsByIds([$tagId]); $tag = current($tags); if (!$this->tagManager->canUserSeeTag($tag, $this->user)) { throw new PreconditionFailed('Tag with id ' . $tagId . ' does not exist, cannot assign'); } if (!$this->tagManager->canUserAssignTag($tag, $this->user)) { throw new Forbidden('No permission to assign tag ' . $tagId); } $this->tagMapper->assignTags($this->objectId, $this->objectType, $tagId); } catch (TagNotFoundException $e) { throw new PreconditionFailed('Tag with id ' . $tagId . ' does not exist, cannot assign'); } } function createDirectory($name) { throw new Forbidden('Permission denied to create collections'); } function getChild($tagId) { try { if ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true)) { $tag = $this->tagManager->getTagsByIds([$tagId]); $tag = current($tag); if ($this->tagManager->canUserSeeTag($tag, $this->user)) { return $this->makeNode($tag); } } throw new NotFound('Tag with id ' . $tagId . ' not present for object ' . $this->objectId); } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); } catch (TagNotFoundException $e) { throw new NotFound('Tag with id ' . $tagId . ' not found', 0, $e); } } function getChildren() { $tagIds = current($this->tagMapper->getTagIdsForObjects([$this->objectId], $this->objectType)); if (empty($tagIds)) { return []; } $tags = $this->tagManager->getTagsByIds($tagIds); // filter out non-visible tags $tags = array_filter($tags, function($tag) { return $this->tagManager->canUserSeeTag($tag, $this->user); }); return array_values(array_map(function($tag) { return $this->makeNode($tag); }, $tags)); } function childExists($tagId) { try { $result = ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true)); if ($result) { $tags = $this->tagManager->getTagsByIds([$tagId]); $tag = current($tags); if (!$this->tagManager->canUserSeeTag($tag, $this->user)) { return false; } } return $result; } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); } catch (TagNotFoundException $e) { return false; } } function delete() { throw new Forbidden('Permission denied to delete this collection'); } function getName() { return $this->objectId; } function setName($name) { throw new Forbidden('Permission denied to rename this collection'); } /** * Returns the last modification time, as a unix timestamp * * @return int */ function getLastModified() { return null; } /** * Create a sabre node for the mapping of the * given system tag to the collection's object * * @param ISystemTag $tag * * @return SystemTagMappingNode */ private function makeNode(ISystemTag $tag) { return new SystemTagMappingNode( $tag, $this->objectId, $this->objectType, $this->user, $this->tagManager, $this->tagMapper ); } } lib/SystemTag/SystemTagsByIdCollection.php 0000604 00000010311 15247100404 0014617 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\ICollection; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\TagNotFoundException; use OCP\IGroupManager; use OCP\IUserSession; class SystemTagsByIdCollection implements ICollection { /** * @var ISystemTagManager */ private $tagManager; /** * @var IGroupManager */ private $groupManager; /** * @var IUserSession */ private $userSession; /** * SystemTagsByIdCollection constructor. * * @param ISystemTagManager $tagManager * @param IUserSession $userSession * @param IGroupManager $groupManager */ public function __construct( ISystemTagManager $tagManager, IUserSession $userSession, IGroupManager $groupManager ) { $this->tagManager = $tagManager; $this->userSession = $userSession; $this->groupManager = $groupManager; } /** * Returns whether the currently logged in user is an administrator * * @return bool true if the user is an admin */ private function isAdmin() { $user = $this->userSession->getUser(); if ($user !== null) { return $this->groupManager->isAdmin($user->getUID()); } return false; } /** * @param string $name * @param resource|string $data Initial payload * @throws Forbidden */ function createFile($name, $data = null) { throw new Forbidden('Cannot create tags by id'); } /** * @param string $name */ function createDirectory($name) { throw new Forbidden('Permission denied to create collections'); } /** * @param string $name */ function getChild($name) { try { $tag = $this->tagManager->getTagsByIds([$name]); $tag = current($tag); if (!$this->tagManager->canUserSeeTag($tag, $this->userSession->getUser())) { throw new NotFound('Tag with id ' . $name . ' not found'); } return $this->makeNode($tag); } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); } catch (TagNotFoundException $e) { throw new NotFound('Tag with id ' . $name . ' not found', 0, $e); } } function getChildren() { $visibilityFilter = true; if ($this->isAdmin()) { $visibilityFilter = null; } $tags = $this->tagManager->getAllTags($visibilityFilter); return array_map(function($tag) { return $this->makeNode($tag); }, $tags); } /** * @param string $name */ function childExists($name) { try { $tag = $this->tagManager->getTagsByIds([$name]); $tag = current($tag); if (!$this->tagManager->canUserSeeTag($tag, $this->userSession->getUser())) { return false; } return true; } catch (\InvalidArgumentException $e) { throw new BadRequest('Invalid tag id', 0, $e); } catch (TagNotFoundException $e) { return false; } } function delete() { throw new Forbidden('Permission denied to delete this collection'); } function getName() { return 'systemtags'; } function setName($name) { throw new Forbidden('Permission denied to rename this collection'); } /** * Returns the last modification time, as a unix timestamp * * @return int */ function getLastModified() { return null; } /** * Create a sabre node for the given system tag * * @param ISystemTag $tag * * @return SystemTagNode */ private function makeNode(ISystemTag $tag) { return new SystemTagNode($tag, $this->userSession->getUser(), $this->isAdmin(), $this->tagManager); } } lib/SystemTag/SystemTagMappingNode.php 0000604 00000007227 15247100404 0014006 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\SystemTag; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagNotFoundException; use OCP\IUser; /** * Mapping node for system tag to object id */ class SystemTagMappingNode implements \Sabre\DAV\INode { /** * @var ISystemTag */ protected $tag; /** * @var string */ private $objectId; /** * @var string */ private $objectType; /** * User * * @var IUser */ protected $user; /** * @var ISystemTagManager */ protected $tagManager; /** * @var ISystemTagObjectMapper */ private $tagMapper; /** * Sets up the node, expects a full path name * * @param ISystemTag $tag system tag * @param string $objectId * @param string $objectType * @param IUser $user user * @param ISystemTagManager $tagManager * @param ISystemTagObjectMapper $tagMapper */ public function __construct( ISystemTag $tag, $objectId, $objectType, IUser $user, ISystemTagManager $tagManager, ISystemTagObjectMapper $tagMapper ) { $this->tag = $tag; $this->objectId = $objectId; $this->objectType = $objectType; $this->user = $user; $this->tagManager = $tagManager; $this->tagMapper = $tagMapper; } /** * Returns the object id of the relationship * * @return string object id */ public function getObjectId() { return $this->objectId; } /** * Returns the object type of the relationship * * @return string object type */ public function getObjectType() { return $this->objectType; } /** * Returns the system tag represented by this node * * @return ISystemTag system tag */ public function getSystemTag() { return $this->tag; } /** * Returns the id of the tag * * @return string */ public function getName() { return $this->tag->getId(); } /** * Renames the node * * @param string $name The new name * * @throws MethodNotAllowed not allowed to rename node */ public function setName($name) { throw new MethodNotAllowed(); } /** * Returns null, not supported * */ public function getLastModified() { return null; } /** * Delete tag to object association */ public function delete() { try { if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) { throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found'); } if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) { throw new Forbidden('No permission to unassign tag ' . $this->tag->getId()); } $this->tagMapper->unassignTags($this->objectId, $this->objectType, $this->tag->getId()); } catch (TagNotFoundException $e) { // can happen if concurrent deletion occurred throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e); } } } bin/chunkperf.php 0000604 00000004223 15247100404 0010004 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ require '../../../../3rdparty/autoload.php'; if ($argc !== 6) { echo "Invalid number of arguments" . PHP_EOL; exit; } /** * @param \Sabre\DAV\Client $client * @param $uploadUrl * @return mixed */ function request($client, $method, $uploadUrl, $data = null, $headers = []) { echo "$method $uploadUrl ... "; $t0 = microtime(true); $result = $client->request($method, $uploadUrl, $data, $headers); $t1 = microtime(true); echo $result['statusCode'] . " - " . ($t1 - $t0) . ' seconds' . PHP_EOL; if (!in_array($result['statusCode'], [200, 201])) { echo $result['body'] . PHP_EOL; } return $result; } $baseUri = $argv[1]; $userName = $argv[2]; $password = $argv[3]; $file = $argv[4]; $chunkSize = $argv[5] * 1024 * 1024; $client = new \Sabre\DAV\Client([ 'baseUri' => $baseUri, 'userName' => $userName, 'password' => $password ]); $transfer = uniqid('transfer', true); $uploadUrl = "$baseUri/uploads/$userName/$transfer"; request($client, 'MKCOL', $uploadUrl); $size = filesize($file); $stream = fopen($file, 'r'); $index = 0; while(!feof($stream)) { request($client, 'PUT', "$uploadUrl/$index", fread($stream, $chunkSize)); $index++; } $destination = pathinfo($file, PATHINFO_BASENAME); //echo "Moving $uploadUrl/.file to it's final destination $baseUri/files/$userName/$destination" . PHP_EOL; request($client, 'MOVE', "$uploadUrl/.file", null, [ 'Destination' => "$baseUri/files/$userName/$destination" ]); appinfo/app.php 0000604 00000004144 15247100404 0007465 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ use OCA\DAV\AppInfo\Application; use OCA\DAV\CardDAV\CardDavBackend; use Symfony\Component\EventDispatcher\GenericEvent; $app = new Application(); $app->registerHooks(); \OC::$server->registerService('CardDAVSyncService', function() use ($app) { return $app->getSyncService(); }); $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener('OCP\Federation\TrustedServerEvent::remove', function(GenericEvent $event) use ($app) { /** @var CardDavBackend $cardDavBackend */ $cardDavBackend = $app->getContainer()->query(CardDavBackend::class); $addressBookUri = $event->getSubject(); $addressBook = $cardDavBackend->getAddressBooksByUri('principals/system/system', $addressBookUri); if (!is_null($addressBook)) { $cardDavBackend->deleteAddressBook($addressBook['id']); } } ); $cm = \OC::$server->getContactsManager(); $cm->register(function() use ($cm, $app) { $user = \OC::$server->getUserSession()->getUser(); if (is_null($user)) { return; } if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes') { // Don't include system users // This prevents user enumeration in the contacts menu and the mail app return; } $app->setupContactsProvider($cm, $user->getUID()); }); appinfo/v1/publicwebdav.php 0000604 00000007636 15247100404 0011713 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ // load needed apps $RUNTIME_APPTYPES = ['filesystem', 'authentication', 'logging']; OC_App::loadApps($RUNTIME_APPTYPES); OC_Util::obEnd(); \OC::$server->getSession()->close(); // Backends $authBackend = new OCA\DAV\Connector\PublicAuth( \OC::$server->getRequest(), \OC::$server->getShareManager(), \OC::$server->getSession() ); $authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend); $serverFactory = new OCA\DAV\Connector\Sabre\ServerFactory( \OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), \OC::$server->getUserSession(), \OC::$server->getMountManager(), \OC::$server->getTagManager(), \OC::$server->getRequest(), \OC::$server->getPreviewManager() ); $requestUri = \OC::$server->getRequest()->getRequestUri(); $linkCheckPlugin = new \OCA\DAV\Files\Sharing\PublicLinkCheckPlugin(); $filesDropPlugin = new \OCA\DAV\Files\Sharing\FilesDropPlugin(); $server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function (\Sabre\DAV\Server $server) use ($authBackend, $linkCheckPlugin, $filesDropPlugin) { $isAjax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest'); $federatedSharingApp = new \OCA\FederatedFileSharing\AppInfo\Application(); $federatedShareProvider = $federatedSharingApp->getFederatedShareProvider(); if ($federatedShareProvider->isOutgoingServer2serverShareEnabled() === false && !$isAjax) { // this is what is thrown when trying to access a non-existing share throw new \Sabre\DAV\Exception\NotAuthenticated(); } $share = $authBackend->getShare(); $owner = $share->getShareOwner(); $isReadable = $share->getPermissions() & \OCP\Constants::PERMISSION_READ; $fileId = $share->getNodeId(); // FIXME: should not add storage wrappers outside of preSetup, need to find a better way $previousLog = \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false); \OC\Files\Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) { return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => $share->getPermissions() | \OCP\Constants::PERMISSION_SHARE)); }); \OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($previousLog); OC_Util::tearDownFS(); OC_Util::setupFS($owner); $ownerView = new \OC\Files\View('/'. $owner . '/files'); $path = $ownerView->getPath($fileId); $fileInfo = $ownerView->getFileInfo($path); $linkCheckPlugin->setFileInfo($fileInfo); // If not readble (files_drop) enable the filesdrop plugin if (!$isReadable) { $filesDropPlugin->enable(); } $view = new \OC\Files\View($ownerView->getAbsolutePath($path)); $filesDropPlugin->setView($view); return $view; }); $server->addPlugin($linkCheckPlugin); $server->addPlugin($filesDropPlugin); // And off we go! $server->exec(); appinfo/v1/caldav.php 0000604 00000006203 15247100404 0010463 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@owncloud.com> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ // Backends use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Connector\LegacyDAVACL; use OCA\DAV\CalDAV\CalendarRoot; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; use OCA\DAV\Connector\Sabre\MaintenancePlugin; use OCA\DAV\Connector\Sabre\Principal; $authBackend = new Auth( \OC::$server->getSession(), \OC::$server->getUserSession(), \OC::$server->getRequest(), \OC::$server->getTwoFactorAuthManager(), \OC::$server->getBruteForceThrottler(), 'principals/' ); $principalBackend = new Principal( \OC::$server->getUserManager(), \OC::$server->getGroupManager(), 'principals/' ); $db = \OC::$server->getDatabaseConnection(); $userManager = \OC::$server->getUserManager(); $random = \OC::$server->getSecureRandom(); $dispatcher = \OC::$server->getEventDispatcher(); $calDavBackend = new CalDavBackend($db, $principalBackend, $userManager, $random, $dispatcher, true); $debugging = \OC::$server->getConfig()->getSystemValue('debug', false); // Root nodes $principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend); $principalCollection->disableListing = !$debugging; // Disable listing $addressBookRoot = new CalendarRoot($principalBackend, $calDavBackend); $addressBookRoot->disableListing = !$debugging; // Disable listing $nodes = array( $principalCollection, $addressBookRoot, ); // Fire up server $server = new \Sabre\DAV\Server($nodes); $server::$exposeVersion = false; $server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); $server->setBaseUri($baseuri); // Add plugins $server->addPlugin(new MaintenancePlugin()); $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud')); $server->addPlugin(new \Sabre\CalDAV\Plugin()); $server->addPlugin(new LegacyDAVACL()); if ($debugging) { $server->addPlugin(new Sabre\DAV\Browser\Plugin()); } $server->addPlugin(new \Sabre\DAV\Sync\Plugin()); $server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); $server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin()); $server->addPlugin(new \OCA\DAV\CalDAV\Schedule\IMipPlugin( \OC::$server->getMailer(), \OC::$server->getLogger(), new \OC\AppFramework\Utility\TimeFactory())); $server->addPlugin(new ExceptionLoggerPlugin('caldav', \OC::$server->getLogger())); // And off we go! $server->exec(); appinfo/v1/carddav.php 0000604 00000006007 15247100404 0010637 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@owncloud.com> * @author Georg Ehrke <georg@owncloud.com> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ // Backends use OCA\DAV\CardDAV\AddressBookRoot; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\Connector\LegacyDAVACL; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; use OCA\DAV\Connector\Sabre\MaintenancePlugin; use OCA\DAV\Connector\Sabre\Principal; use Sabre\CardDAV\Plugin; $authBackend = new Auth( \OC::$server->getSession(), \OC::$server->getUserSession(), \OC::$server->getRequest(), \OC::$server->getTwoFactorAuthManager(), \OC::$server->getBruteForceThrottler(), 'principals/' ); $principalBackend = new Principal( \OC::$server->getUserManager(), \OC::$server->getGroupManager(), 'principals/' ); $db = \OC::$server->getDatabaseConnection(); $cardDavBackend = new CardDavBackend($db, $principalBackend, \OC::$server->getUserManager(), \OC::$server->getEventDispatcher()); $debugging = \OC::$server->getConfig()->getSystemValue('debug', false); // Root nodes $principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend); $principalCollection->disableListing = !$debugging; // Disable listing $addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend); $addressBookRoot->disableListing = !$debugging; // Disable listing $nodes = array( $principalCollection, $addressBookRoot, ); // Fire up server $server = new \Sabre\DAV\Server($nodes); $server::$exposeVersion = false; $server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); $server->setBaseUri($baseuri); // Add plugins $server->addPlugin(new MaintenancePlugin()); $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud')); $server->addPlugin(new Plugin()); $server->addPlugin(new LegacyDAVACL()); if ($debugging) { $server->addPlugin(new Sabre\DAV\Browser\Plugin()); } $server->addPlugin(new \Sabre\DAV\Sync\Plugin()); $server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin()); $server->addPlugin(new \OCA\DAV\CardDAV\ImageExportPlugin(new \OCA\DAV\CardDAV\PhotoCache(\OC::$server->getAppDataDir('dav-photocache')))); $server->addPlugin(new ExceptionLoggerPlugin('carddav', \OC::$server->getLogger())); // And off we go! $server->exec(); appinfo/v1/webdav.php 0000604 00000004623 15247100404 0010505 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@owncloud.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ // no php execution timeout for webdav if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { @set_time_limit(0); } ignore_user_abort(true); // Turn off output buffering to prevent memory problems \OC_Util::obEnd(); $serverFactory = new \OCA\DAV\Connector\Sabre\ServerFactory( \OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), \OC::$server->getUserSession(), \OC::$server->getMountManager(), \OC::$server->getTagManager(), \OC::$server->getRequest(), \OC::$server->getPreviewManager() ); // Backends $authBackend = new \OCA\DAV\Connector\Sabre\Auth( \OC::$server->getSession(), \OC::$server->getUserSession(), \OC::$server->getRequest(), \OC::$server->getTwoFactorAuthManager(), \OC::$server->getBruteForceThrottler(), 'principals/' ); $authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend); $bearerAuthPlugin = new \OCA\DAV\Connector\Sabre\BearerAuth( \OC::$server->getUserSession(), \OC::$server->getSession(), \OC::$server->getRequest() ); $authPlugin->addBackend($bearerAuthPlugin); $requestUri = \OC::$server->getRequest()->getRequestUri(); $server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, function() { // use the view for the logged in user return \OC\Files\Filesystem::getView(); }); $dispatcher = \OC::$server->getEventDispatcher(); // allow setup of additional plugins $event = new \OCP\SabrePluginEvent($server); $dispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event); // And off we go! $server->exec(); appinfo/info.xml 0000604 00000003105 15247100404 0007645 0 ustar 00 <?xml version="1.0"?> <info> <id>dav</id> <name>WebDAV</name> <description>WebDAV endpoint</description> <licence>AGPL</licence> <author>owncloud.org</author> <version>1.3.0</version> <default_enable/> <types> <filesystem/> </types> <namespace>DAV</namespace> <public> <webdav>appinfo/v1/publicwebdav.php</webdav> </public> <dependencies> <nextcloud min-version="12" max-version="12" /> </dependencies> <background-jobs> <job>OCA\DAV\CardDAV\SyncJob</job> </background-jobs> <repair-steps> <post-migration> <step>OCA\DAV\Migration\FixBirthdayCalendarComponent</step> <step>OCA\DAV\Migration\CalDAVRemoveEmptyValue</step> <step>OCA\DAV\Migration\BuildCalendarSearchIndex</step> </post-migration> </repair-steps> <commands> <command>OCA\DAV\Command\CreateAddressBook</command> <command>OCA\DAV\Command\CreateCalendar</command> <command>OCA\DAV\Command\SyncBirthdayCalendar</command> <command>OCA\DAV\Command\SyncSystemAddressBook</command> </commands> <activity> <filters> <filter>OCA\DAV\CalDAV\Activity\Filter\Calendar</filter> <filter>OCA\DAV\CalDAV\Activity\Filter\Todo</filter> </filters> <settings> <setting>OCA\DAV\CalDAV\Activity\Setting\Calendar</setting> <setting>OCA\DAV\CalDAV\Activity\Setting\Event</setting> <setting>OCA\DAV\CalDAV\Activity\Setting\Todo</setting> </settings> <providers> <provider>OCA\DAV\CalDAV\Activity\Provider\Calendar</provider> <provider>OCA\DAV\CalDAV\Activity\Provider\Event</provider> <provider>OCA\DAV\CalDAV\Activity\Provider\Todo</provider> </providers> </activity> </info> appinfo/v2/remote.php 0000604 00000002116 15247100404 0010524 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ // no php execution timeout for webdav if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { @set_time_limit(0); } ignore_user_abort(true); // Turn off output buffering to prevent memory problems \OC_Util::obEnd(); $request = \OC::$server->getRequest(); $server = new \OCA\DAV\Server($request, $baseuri); $server->exec(); appinfo/signature.json 0000604 00000115307 15247100404 0011074 0 ustar 00 { "hashes": { "appinfo\/app.php": "d47e6c58cec5d0b22870c6b38664f15e501e83506350a13534a8d4d09456753af0805341e5a6c803b5bca5a8087e7cba85dca5f5f2d2786fb96bba9fdaddc158", "appinfo\/database.xml": "08bff56cf709d01611903a62018bfe8847aa0d62dff72eb3485f68357cbb5b03b62bfebc1e09ed27ae31d66643df68d231563434964c565c841a4e485556914d", "appinfo\/info.xml": "0d3295491f76be6ac60859b32ceeb131ec5c27dfcc37c5bd00ed73771865a950f1f2150bab4bda4e5aa782bbd55bc5470a6ae25bd3aa08f082f62c495cb7113a", "appinfo\/v1\/caldav.php": "29f08fb1f47d05dd84251575df5daefbfa7fba4d1564e44c30be89029930ed0d1e8ae56e9a08a02975071dbd431d9ee1cee673084d95de27109b3ff9963f9d6b", "appinfo\/v1\/carddav.php": "7567b7d9d6ff872d60df482054d24d00b999749835d67f7f6b8b1f2bc45ba860098810edf89e6e44cb3d473a0b33179b1c182237f158b86097edb823495a5de6", "appinfo\/v1\/publicwebdav.php": "8e7a2ddabeae87d5e21e90882997f6b0bb0ddc93b442a74d210f8805ae0674cb312fbbf428473ebad2aa93531a1bba1041d02adc15fd15499b2a2d71fe5e9d0f", "appinfo\/v1\/webdav.php": "b0d574a416e41ed1265444eaf24769edccd865acb9bc57a815ad184bc674b498af94da54019966a3b8cf8ed9fcff7591641febc6e8d79100e0823177b523f077", "appinfo\/v2\/remote.php": "542dabe81277d653771e24fbeab51c268ca19726facd9751a326fb7c5ceae6170246fa62ec314fdd39a60f9b917f06b1c1872c9539017be53ef937234ca213df", "bin\/chunkperf.php": "e063a573b07d187e7cc4df0e029b662654a502c70889431bf925bdaed415c589e81aae2d05f356d9ab8a390b5d7f5deaed5841658d63c7bc9b374715856dc140", "l10n\/.gitkeep": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", "l10n\/bg.js": "d683d9354250322b51f8ddcb8aa1bc9a5bb765b3cc8831080dad9d705f7637f03fecd7a22541c7b3610238727698be17eaf15051cdadab71f1ac3b5ac8d9ad4a", "l10n\/bg.json": "418c33fdfc432043bb56301bebdd5e84c97ef61617ef9b31c41b7ad7dc017dd4e9e2fc9402d06780f66053bce23931b524c5944cebeb2b3d6105752e94885c86", "l10n\/bg_BG.js": "d683d9354250322b51f8ddcb8aa1bc9a5bb765b3cc8831080dad9d705f7637f03fecd7a22541c7b3610238727698be17eaf15051cdadab71f1ac3b5ac8d9ad4a", "l10n\/bg_BG.json": "418c33fdfc432043bb56301bebdd5e84c97ef61617ef9b31c41b7ad7dc017dd4e9e2fc9402d06780f66053bce23931b524c5944cebeb2b3d6105752e94885c86", "l10n\/cs.js": "27d1eefcc934049f509e156eaff0b62886302ee8b277784f20626fe4b560304c6bc72fa79652d7ba5ba41b2329ef2025b13566d5143e665ad0f22dd5b51ff0a2", "l10n\/cs.json": "e1990502f52114f517a687f2123eea5ee4611308867c4960ffef2ed46a5f36f6c6f0ee5db89b22e323819a6e0d04d095f34c591c6f861857f14d73d5462c22db", "l10n\/cs_CZ.js": "27d1eefcc934049f509e156eaff0b62886302ee8b277784f20626fe4b560304c6bc72fa79652d7ba5ba41b2329ef2025b13566d5143e665ad0f22dd5b51ff0a2", "l10n\/cs_CZ.json": "e1990502f52114f517a687f2123eea5ee4611308867c4960ffef2ed46a5f36f6c6f0ee5db89b22e323819a6e0d04d095f34c591c6f861857f14d73d5462c22db", "l10n\/da.js": "b8f06090e35871d2cf78d6ae1adcfea999fd94f604958c483b207281ec8ddd5c11fa4cbfd8b34c1b1a5fbf56ab2609151ca58394edbf43dc6446bfb3d59be25f", "l10n\/da.json": "68a72967224623801c8675ad699e0637008f4a6ae589a5116bde042f3931697a2b0cb0679fcd15779842b71c54a440669e776677b78539211bfce9c23c99791e", "l10n\/de.js": "a762dbf6e5ef9b698ea84f1e2644692f61c1a70d074705a1d13df008d53902d7425a3a23a0461bd33c6b412cf5f6928a29a2c0ef9010827c4036e1aac18263b2", "l10n\/de.json": "2ee152a7c2b8f1d6c24b6db1f37bdad68a3a5092af75e61f07225f50a6f33915850f4dc063a661cb30ada1dbf70eeddbb5b5277e72a2bdfc5f4fbfbaddb8cd47", "l10n\/de_DE.js": "fcac92d592dc84215bf95e26c1974e2aa32d329d3960c4f912b8617787a71dcf2184d999b79f22e93b6ffde87d5ee2bd873025db301dbd5a818a7209479ef250", "l10n\/de_DE.json": "c9d02595f58cf27d4246acd270ac66175fc8d0137a297d85ae5ef76fc40db4510ced686befc5384ef68d4586de787f682f40ef22598c58287786ea46aeb839ab", "l10n\/el.js": "4ddd16125e0da709ab09989ce6c6282a96ee70190bd4fdc469938a53014324fca9280463acf3a196dac1532a70070abbdfa515e43ca35253a6b15b1ecd34e838", "l10n\/el.json": "16278190fe8f54d909401675a43da7b1ffeb604530bacc737bf6c4614d72ba6e211da31eff8f3a5bace12a2c8e0653f45d1f547f2f30de0aff3b955b4695d6df", "l10n\/en_GB.js": "04cca40436fc0c522977fa6e7720d70839654e0d3ea28ac4593fe071e51ca967dfa3edb3637e2769eb94df2da93152802b5445d4d38282af07ee4b3d6293ba2b", "l10n\/en_GB.json": "23f91be3ed8d4e70fe8d7ca80017d2cd45cfe08fab5bfa90016df319f9c370531b4dfdeef7578ecce3f61bac8eb1c032f0c67c783094e4a30b758bdf30c211b4", "l10n\/es.js": "bbcac1d099725422e661eb8c4ce19cc20d22c1196212dd9db0613d186e3ca6575f2fbacc928121581fe9a1cdb7ce93ec03b19e39f75c2d54979a8c890b8758d6", "l10n\/es.json": "40d1d3fb310e6ed488e25a0bb2ef981d899dce2e24e963b1154cf44caf34019269c270a42d5d8d1b1df501608cf47ca5b7c432b8b6119ecd2303eec4930f756d", "l10n\/es_AR.js": "3b371086bf6efc5cc8c7ac20296eb8896869142783f6dcbab3edeed9cfab97fcf0da2071e5fab4120e0d76c47d89a573b471e9c457120f122b6ab1004cb1ddff", "l10n\/es_AR.json": "d993bb25c6e45ee2d4735aef67978dc46959526e2c0c7614e58563005cc079d319cc0e02f3ca459200153fd886ba2e21363dcc1a9afc3d7474bf5cf72fc4132b", "l10n\/es_MX.js": "4837c2ef0a6c2d46688febb38fd64cc0ded19034b93123e6515dd8300d6e6d37b0203915988158c3f9f46caa0b70cb7b2dcf4ec549bb7ac000356e1e9a5b9e2e", "l10n\/es_MX.json": "b6417c5292c1c4490c43cd886923f7f6bdf3d3a6c0db6087f3c85d5e0d0b270c0f6b05429cdc4556ccdd1c9dca2127f674cb999219e4d80ead6eb52666ce0990", "l10n\/fi.js": "ed5da9a451ebe3f2c1ce75afdafe7be267c0933d9e57f2d66dbb32881bded0a0d1c44e5b3aa22a2130c5272cd8070ef447809ca1cfdbab4c18f5a86ac05f489c", "l10n\/fi.json": "c88dd512cacd1577b6272042f0a8d33c86920b22e9c9fec7a8888dfc019789058bd000fbf9b11d9cddf116b06386c720099342d676c39b6d9cd94f9bdd0db2fc", "l10n\/fi_FI.js": "b21dd905a2811c633cfefcae4648219f25ef49cd81e14fe34c06119d0e7d0858d15723b2ed53ac20d69e57ded3c9aee348eacded2e66bfe31b059a9a20956a99", "l10n\/fi_FI.json": "1402f1b949ef69c94d89ea39a5c1d5c7a2eb70823d9295bc819d3911f8ef548d97c67e8acdcb6f325c1203adc13a11316a1c7a191bf99ef5231a5526585329a9", "l10n\/fr.js": "7b5d8a37cf9b20fe704039522b726748a4c9426a17b4f58b5318f1dc824de1e7735cda72ae5d35ced623b8d22de6921103a67f5adbf3b57b60d722f08441d663", "l10n\/fr.json": "d03788d5b22bb6f94028d60fd75399254dafbf0cdad1db66154756b0b3c867f8090e25a8490513205aa688fd17e32a3d041695f86610872b5e5abd0436ddf079", "l10n\/gl.js": "1ebf4e26cd3fcc461403159e774e8ffb6dce5805b00b7930f2ed4c226850a0e9733efad8354e7f1f8dcc317ee46efa560d703c8f76338b0d591e4a731fab783b", "l10n\/gl.json": "86af76f54476b7c993d9299cb1ca970f39c1a19d3f545f8d98c418489c321432d852a9462af859c0a77fa462ec477305ea62398c8a9a3f80b57acbb5356ec371", "l10n\/hu.js": "c0ee684cff371663ec250b4a7d9d95700880bdb34ae7320461690779cce793e4481b11a221cce86069fcd271676645a9d958ab2cf85243556d5fc192529e8f63", "l10n\/hu.json": "4666f87c75695e0fc92af2dd2ebd79f7e8e1030c7dd7657e5d79651d6a4e38233532133d3b7f4038b7f3f03da72039d009e8c37d9c73e92c1dd957eabcfb4653", "l10n\/hu_HU.js": "c0ee684cff371663ec250b4a7d9d95700880bdb34ae7320461690779cce793e4481b11a221cce86069fcd271676645a9d958ab2cf85243556d5fc192529e8f63", "l10n\/hu_HU.json": "4666f87c75695e0fc92af2dd2ebd79f7e8e1030c7dd7657e5d79651d6a4e38233532133d3b7f4038b7f3f03da72039d009e8c37d9c73e92c1dd957eabcfb4653", "l10n\/id.js": "8a74e91d8232192c89057f86fcb28e0a8c452647751543cbb47313dcf40346b4828aef417de7e4e1c9f5dad0bcec7ad527feb8d370884fbe186bc5373a96bf23", "l10n\/id.json": "8c4ab866dd746bec318e72ae02cb80e302153dd8020d9c5715649c1da6bd8e4849e9d1776512b6a4963dcc4505823acb02663f50793bcb48c9d657181ab575c5", "l10n\/is.js": "9b6521d931d52a154391b8e60c0a886e249299b1dc939f5648299a7500aaa504a1656795743d5cdbc278946440bee4f9aa1ae6523230f9f828304a4ebb99599a", "l10n\/is.json": "c429b90372907c191f49317587734de2667dfbeb63ec7086b3b748acb2023d0dfb7ac2598072ce253d7111a70e3c3e3740aed407e9c7f74fa29af1a50e3d3845", "l10n\/it.js": "39a5eb2bf4b5bc2c060ee48f64277b8edf8b439d7f7315157a9c2fa6dc5e612e6fd629b12e21670512c42ba758587f684a09aebf9ac4b97b8fb01eaaaa894a9f", "l10n\/it.json": "520e5bef33533afd297cb256d653ba8a0ef2e49c131a25cdb8b0debdf7155a5703d8b8fa35b2307f7e0f200ac6991b9e6b9869534990976605cdefb5ef331610", "l10n\/ja.js": "c073932fe8ee5201583a389c043dd385c9b1334ab8f953c9fcbdc5c0300fe69159eb87bd7665b00fe21851d3234b2459a85fcd482d17efdaa191b9fcc1d6600c", "l10n\/ja.json": "570eaa698d9bb637fddf5923bef95056a46eeabf3953a6095f40014663db8269b58b56ae55cb5f79ef88f147da3165a65d3832b50105b3fa8fea3f31d6d088c9", "l10n\/ko.js": "aab74468cb70482e3e2a4cbc1e31f1746b42b4f446ccbdce8e30455f447b13fcc778765d7115961de742a8f89db1872eb5e16cf7ebabbaa8e28dd27504f0adac", "l10n\/ko.json": "25cc87c898152c58fc331fa04077cf85e999ebedd6fcb0f4b73df595c17ba47f77136e9cbabb198bd714e3eb85edb20e3376fad97db9d1ef61f056c7eb375511", "l10n\/lt_LT.js": "4751e2024c8c1abae399e0fbbdd6d1f284ba25ea0491cc15d930028d304d7f03d2d31ddc0cafa471d3b816311b412dca58d2597283fbdbf86aae5e0620ad02c1", "l10n\/lt_LT.json": "00160d76e1673f75476349d3c06aca2b36d0998fc02f1bf98e22ab9f442b0ae8167d0a96efe06bf133200fa14864c98032a76bb40485ba2f3ad6e98afe0cc250", "l10n\/lv.js": "ab907f135a0d02ea2d05a3806b49646f07dd476b58e8967aad82d97f6563a81f42397c8174df3611d3c88c79dfd53d166053b36a23ff5e6294e905a17d1b126e", "l10n\/lv.json": "c950bb3c446a05d955b98ecd5676dfdf4f077c209b365930839019c31773505e0418e78a9664348adde54028ae83cd2a1659e0194a97a3c71ac6f844c28a9a13", "l10n\/nb.js": "dac156b5601dbb0944571661e6ddd7e3aa063e20d39a84014073be0a6034c8321bfadb3446eba8ac2c4bdcd8c46362fd8e2ac4084ff410d7974f501a199deac1", "l10n\/nb.json": "3f027d72ba33611f4fbe3f243e2ec122c820235e56efd9565daab928ea82ee90c427fa70c730b8a56cd20301cf00cc88fc9297ede076c853535664d7cbe7012b", "l10n\/nb_NO.js": "48aee1d7a62acee4bef3fa6eb1ba32256c4f624ecdbed2d51e84434104e2846d00087cfea0e5ceed3303a326906dd7c05ef5747b89c4f06f2769b63d27bf5f1b", "l10n\/nb_NO.json": "cb220c55630103cb1b5c8e757dccbc6a5696b73210510d3f33dd5c1b10c69254ef40ed1ce2d86294ec0e652c0ba8a2ffda5298e5a09c13d846aa2d97e7589d96", "l10n\/nl.js": "0099840ec8f37e0d50f53695c2e9d0962f6f06a388798af28656cabe028367d2e74193b6f79db397c689f58aacaa6dd17407afc6945c80cd017ca6a4912c3af9", "l10n\/nl.json": "7e70f7573e9d8501934c32a9f107764030f7d744269afd41f05dd954b7830cfc20d5cf11642bb3dc53b3e23d0e26aa4db8e0dc98e18498a21dd40af4a98de843", "l10n\/pl.js": "46b68d9a401eafed3d4192e1513b67f2c56855939898c9c5b2402600524751e94a9f6c3c780d0fbb10d5924184e4d19c83740658231b631b04a5409b57d49173", "l10n\/pl.json": "6e2d55e31e834e5de4e93872029f2b1c9b1628c2a8d9a5aaebc4ece74b61da5b7f928fe453e2a248427d067f604910705ca1c7d4c644e26e37ec2399bce65063", "l10n\/pt_BR.js": "68b1b8f93694139a752a53cf7f113bb1e96465dee69a29139045457359d4fa46d59cb09a9ed880ba009862e683f60375f89ece19bb18c3596379cb8e98ea833f", "l10n\/pt_BR.json": "38471a5f33f176d26f41e8e0efc429a8e45542bd45dbee514280f9f16f69df24a5e4027005f7445ccd599ba23b4a30f12ab00b186e754c30ff8c08f1b73a5295", "l10n\/ro.js": "15d20bdb7683fbc1482b662433b4915472456acef2de31ed98884a039c86fd268fb1e51874cfbd55f154a77526db751ac1aa9f4b5c3941a4e6586c5035a44aa5", "l10n\/ro.json": "1e9b949bc0105e51a6f6f5d746f30556594ff851cb649813b01a136b63de8b0c2158a0b542b3d421db9bba9a7fcd81ed4b28d0f371f5deed5480e622ba4937dc", "l10n\/ru.js": "7a527083bdd5db36a887c26cc140ffee3b2c2fa4324a8fba0b8ba88a46d24023fe587acb87d4fe96e8c61bed7fc73072679efdea0e63718a8d6dee3213c0ee52", "l10n\/ru.json": "68f61e1838a68830a5841597a570d5aa69eab7e112bb4934dc1ca2261383cd3640e2f37a43e548e46952499d361a94f1de94a484d181e233cd381135d0c4b754", "l10n\/sk.js": "de35a403a969611f7abafedae9c463c825e193b7ca33e4353ceff0b2c00d1ef4e16de0be3e9fe1cffd5a3966e8198f6e50e35719e26edf7e873b7337b8971a47", "l10n\/sk.json": "96d4a12ad1b3f70adb65c1c67aafd0e85cf3ffe38c9266e1d6b45b948a83d13ba9053cfe0cc908adfa11a353ee0c5e1b4977bf9820f492ef0ac9f59c1f6ad15d", "l10n\/sl.js": "056c17fe47e8cebe8c312f3f0f951f1120708d85280bdb15904161b9d69b67d4c5ce9cc5e2c91319f3f616ca0d4539f8858c4a8ebf7fb44e6f7e232dcb86c7ad", "l10n\/sl.json": "06d91834773c00dc71e995f74f724e9d070b0129a1ae5117e0bb3ba45c51c3bb2b845ed0d2c198f6ae50e6b34e499f7825883850cacf981f7176544eba7b175d", "l10n\/sq.js": "6607a4f5b3443aac5f46a53d7c599e20bf92ff85a0ac6d9bc98059afe46ebc1763ed66dceaeb78fd0c7cbf4627f48d5c3bfff2b168f16edaa46bddcfd0c7bf87", "l10n\/sq.json": "505f1e04fe9fa4963a0bd93f7f3510ac2bcaba640d05c127b58ae10a36fb0d0beb8d8543b71ffc09f6672b5805c86e3341dfddc5693d0a91e325549408d9c7ed", "l10n\/sv.js": "0924217e177ec8005bcbc1c4a1c5b9eec5bbb04a3257a4af3e64a8ba6b49bcb75f01271689cd046d28022a7be18cf02f6502ec7bec1e20903d7b907b6c70ebb9", "l10n\/sv.json": "cd2bcf85130091289a89c16839a0a2dadbc96af1d09911e13e8c4c7c1aa5c8ab22dd3c7f92a56457dd70f1821dfe07e7de91eef227d594179f91c97649c057bf", "l10n\/tr.js": "6172b567ce9caa9b2e9682b895bd290db7d6fdaf6cf2f3655e02c6a39ebf3355dae08cd655b59714afc4338d99556c5acdee3ee02247a7aeeccd704e56930210", "l10n\/tr.json": "f99f573fec46a580f8b94541dc9ec67e93eb755790702ebac77f992d80ca2a91c0af028891dbc4e4d803dfb6753ad988bb20bc49c83a1fe64061b3c43551c009", "l10n\/zh_CN.js": "90ebf8231ab6379b256eac9fa6fa045f2aca625d7f8c0b2eb672adb96a2782d9e8e9665ba6e0855bb31995bac9a352754017642330a6202e20d5ade6a26851fe", "l10n\/zh_CN.json": "b979b356b2713a8e58c26009fbf9f23c69e43e431a952cfd0d4671d54e28583bfcf37b620abb8df7422d29223a52a0f1582b8df80ae35e0eb02ad64ae8bd0af7", "l10n\/zh_TW.js": "22f19b8d0d88d859274479a4a81b9fd6c644b75849bfea6e2daf03f4c550f7fbabf84fbdfa7b6e1d4bd567cb246f4d63afc40d0adc1d26ec4cd7a9d710147d1e", "l10n\/zh_TW.json": "81f982fb2a54af77322a982508d84dfe599eb788375ef0783c29935f6baa14720466a78661f1fe5690bfaed5672231abe7b6593e7bedbce631a1a569b72ebe8b", "lib\/AppInfo\/Application.php": "23a4793f06a0e1a86d1f5e6aa98e37d5cf7ec1efc3c5d4221a976b3f07e7c6698032a0671f6dc80c95e5535858b85eb8bd65ac76cbe2bed48c6ec44bf4953510", "lib\/Avatars\/AvatarHome.php": "851634741aaeec497f5897e071e5fc38c0d8c50941e3aecd2c0fba542ac4da6e6a2325f8768571e7a30173fb80e26f4c5ab1fb64c4a017701cb6c76d3a8e1230", "lib\/Avatars\/AvatarNode.php": "dfc9823a1a88a759ad7798344e74216e9a0ed58069cafb19bc6c341bff944dae6912d4852fad0205e75e32695ce5376180a078c35e701447c91fdc8deeb1f512", "lib\/Avatars\/RootCollection.php": "0c532c87418dda8b7fa8a72fb7cbc593c5dc947e02db00a679d56b8245144776dcd9ff4b42a3e00297a6f7e564de45953c9baba9156937001b6198a814e9b1d2", "lib\/CalDAV\/Activity\/Backend.php": "74fc6a0b2b8dae08e142637ab23849bef81f3685d64bdf9d79bb5959d711f7aa3afe9827e46cde71dac4a85f808fc374afcaceda681047daf015b810ed1c2037", "lib\/CalDAV\/Activity\/Filter\/Calendar.php": "972597e6dbe254fe316490e1c861178bd7699df8ca731ffd3ef7f6bf9abf8b041e961748cefd961563fe4ab996772a6c6dc6a80ae7fbe5be40b13ae13e54a7ea", "lib\/CalDAV\/Activity\/Filter\/Todo.php": "100f71f680afc5547333dd5214a70131502c98566677756907192aaa0bc109e2111962a4f06ec4dad62bf2f1162e3c259ab803b1cc64207dbf46673736046c61", "lib\/CalDAV\/Activity\/Provider\/Base.php": "6c26bf75f8a93de4f13c3049f145c9f7fbbef72bb4a168ff7b6c108f38dbc2175fdf5aebd8849b3d91dfa11b89b1ef3ff02b6a00a8053e7b62e67d3b74565c56", "lib\/CalDAV\/Activity\/Provider\/Calendar.php": "282c56bbd2727688e8bc7520b5255b54e9d03bf25b21cbf7933027ac4d1456e3e416729426037a236f15b7cfeb956b61031dcb9fb63590afa021492f0075ac16", "lib\/CalDAV\/Activity\/Provider\/Event.php": "a0c8512579e093628576876992b808a4c1e6d0c4b21a797f4ec2cff9fc82c5ba8ec37dfbc13a43449868b98bfef2d41042aea26b5d226f75d235319a838cdf37", "lib\/CalDAV\/Activity\/Provider\/Todo.php": "6d02199a6d5b1cedce9aaf2c51c568e63d1d662512d6b1ed6c6e1cd270eecdb19fa7ec93de679bf1d1f2f4f5327845e629e710b22eb9008f128533e48d354cc5", "lib\/CalDAV\/Activity\/Setting\/Calendar.php": "19bea7b3c5f0f0b31bc77fb86e8ec283d74958900c8af244bba120c5c91285ad5ad13c8ad38bec1876763119aec7e6dd8f8f57a6fa9e678fc828b75994d8eebb", "lib\/CalDAV\/Activity\/Setting\/Event.php": "0d3fdc33a9d456ae9cde467b2fad8d60f9cf9af55896c831a5d2aff74f621ed0170d317f1de57dfc17e63cab8b48f8c7d1035df9cf45ce3e96f07cd925aa4f09", "lib\/CalDAV\/Activity\/Setting\/Todo.php": "aeeeae9931c254f2c614dede3dbe6317040e7e69d4f2ad1e44a8a09f574db801900925f2ccd58974052741d00158e225d203bb76a8ebe7f2807f45d53dea1ff5", "lib\/CalDAV\/BirthdayService.php": "c7446a10a3440f4e218377fe2fea7d5abf49e828c15e33b78e385dd502074c23572d841a1cdd452a38ac740c0e4c346b91f949b0244e1814d2b068085c84c83b", "lib\/CalDAV\/CalDavBackend.php": "299b0fa0aba461920e63a97794c720e2bc7ba0ac15d94ff6ab92e893ab22fe597b1a5cf239b49a7079a7f9086392df34d13381961bb78ea4fb30fd55e21b8611", "lib\/CalDAV\/Calendar.php": "f7c08bd6589072ef8412f2a4c2030c2203a34c3ca7e72bff68b03d0b1c98e46a5fc7199646cc81f61b484411e6bea4f3a8f1eda73ff24efd813628c22d7770d2", "lib\/CalDAV\/CalendarHome.php": "37b737bf2db1be0201efd27626feea792ee2eec7b9a6dbd37f46dd81ec14ac47f54dd4377b3f29ad7a965a0cce782e5479d36e39aa721654c6e31e476ce6e0fe", "lib\/CalDAV\/CalendarObject.php": "60bb7113eab910643dbbfe3c13c8d8ee87b35a2797c12e119a3b302c093cdae2cef0b63f90782132635c3a4dcad8dc7d5974ec556ac89e78c26dc6920432fd29", "lib\/CalDAV\/CalendarRoot.php": "66abdde1105c0e5a1ffdee22a1e09c66c1dc6f7695ea851187b5181de420f8cdfbf4977ef850d222a43b187b67832f3c39708e19762487c79e94830d159507ad", "lib\/CalDAV\/Plugin.php": "9c06df4b27898077e3775c0ff0b3da3cfdc4de31d2c1d49f0d391cdaa057d5a7a5e8d29a4747a8386df0d2522cd2dc54e9a41dcc836e2808165881558e2a12dc", "lib\/CalDAV\/PublicCalendar.php": "09760794dc7df61c9f651e3540a25706857c92f9587a84750071046da2514b91f8038490f4e4f1f4228d2777a5e8347f958cd3c8e9ad425d7321471c84aa73a7", "lib\/CalDAV\/PublicCalendarObject.php": "f672847ab06c868603855924f724556f3407e00731c9d16a1609d30ccb13e2e41468ddf29c26f5103ff736d4d1a9575a5262d702375d145b4bed778c36034d70", "lib\/CalDAV\/PublicCalendarRoot.php": "4f8c0ed9c8e5ff772d6d598da05ecb48101f4b76545d071f0c954b253f84f30b98b7097b1e02a56516c661de8f4958dab28c1027c7f8978936d34bec296ab800", "lib\/CalDAV\/Publishing\/PublishPlugin.php": "bde506dcca6b499c2fca8a355023e9d21ad540e228a82c8aa3a184d7724c477bd6c63f11607faea99518ba34924451952b5980a527bcf30a41c1c35fb61d63db", "lib\/CalDAV\/Publishing\/Xml\/Publisher.php": "063396414268bbd4aa1183d5d4de1522c97a5c09ce0307088a2d6848dbc31daece430d7cdde75d2c796770f18bd8ebef34b5a889221e99ba45cabde46bddd685", "lib\/CalDAV\/Schedule\/IMipPlugin.php": "336b8ead2a5b8991d6fda534b2bcf48458257bba658a0f03e18a4f1d5d0e608a4286539b6e0d83af98c62c7bb77da4851cbf542dee6602a8c6a867464c8197ea", "lib\/CalDAV\/Schedule\/Plugin.php": "7f1741df39ea6b1b28fb6c6c58a1e009fc0725a6e5cda5cf7068b313242d7f9ae1a0a38c7e34df3edded39db4dc7d44c275bb741021e9850b71889146229c174", "lib\/CalDAV\/Search\/SearchPlugin.php": "7b37874866fa7288c3444ec7df1dc682320f3950e850ec1509f467827ab88f7cbf492a5fb1223e5bea9bc8cf0c4d770849b03f40a7b8ec3976f4cedb368a3b59", "lib\/CalDAV\/Search\/Xml\/Filter\/CompFilter.php": "0c640a7b6cbdcf5ddb0e92a60b7acf843414f01d3d44764406dafe1b878f278f58126f8a514dbbe0640f0b8a8ae2e6059dc16f389cceddd2399c5c215fa3e8e9", "lib\/CalDAV\/Search\/Xml\/Filter\/LimitFilter.php": "ba874aa2489e2352b276a33a5d2a6902cc1322ffed66450e9bab0e4f943fae293b2c0f7a1b9cd94ca5fb8f3948b01be8e4f189efde469061392e93d9a969d665", "lib\/CalDAV\/Search\/Xml\/Filter\/OffsetFilter.php": "7e7f585947b07e3d0ff58b95882955b22215f9f0056b657b408408e57d22589836984e22a58b14044e3a30a2eabec57fe29a94b5b6cfa081c98e267a14a39c97", "lib\/CalDAV\/Search\/Xml\/Filter\/ParamFilter.php": "f5ac335e4fed16dbcfc847529534dd13cb1763f143d612f867661a061af427be4d019bd7336007f8f4326613c0c56e2dac2b5efb59b42cb2e7af9616986ade52", "lib\/CalDAV\/Search\/Xml\/Filter\/PropFilter.php": "59afad1ee18772d9d08831e7f5dcc52888227753e92e3e4062d3ce84133f8027cb2b8291937def910a7ae0b6aca18cd8c5d0190720c39447e4ef18ca0206bce6", "lib\/CalDAV\/Search\/Xml\/Filter\/SearchTermFilter.php": "4cc3d3af4ec5d36c1056091feeb3355ad3fe2948960ec3e7cc0ad465d25fed4567b56d6d4905333d13b536ac043bfa54215dee8f1dd3fd3c1895d829c043ef1c", "lib\/CalDAV\/Search\/Xml\/Request\/CalendarSearchReport.php": "cf5bce5262da793b8c1d579ce438a8f1061d0263744a12c68d8d5dd5fdf74c4dacd2d7ca3874afdaf542946cec59dd5a9716e6bb7085d9dc831d293f223929cf", "lib\/Capabilities.php": "5dff92ca155bc0aa1b7f3a9fe3594576a044899a5ecc388002e4f25799c8a459612f33005e4150e179dcd9687c4e0ad9b0756aec0d4663e5f83f7cf0f4059fcf", "lib\/CardDAV\/AddressBook.php": "0722969a0308bc55d9adf1e540177239f068538628b5cf4b885f4f9bbb033a4f2792cc4c3e54309cd3c5c86f6645bd29e63aa96cbb37750fba617085bbb51400", "lib\/CardDAV\/AddressBookImpl.php": "7eef110ef011cc317f77f27e2e1d9d4e2597b89ff35370fd5b89f6885a70492d7ee997721dd95b61a9d4963dc485c9bec2b5fed46d69a2eeb9cc6e1c34e9901f", "lib\/CardDAV\/AddressBookRoot.php": "b390fd638b45c08acb48072ba0dde2a14c7ad2aa5753137db2dcdf387b2a6659050625bc62be0638697de8af1330da8a0e72a1eba518668f4f51fa7c604b700d", "lib\/CardDAV\/CardDavBackend.php": "2a221a936d9f9410245b33b35ae7775c5046a8be9c155e3ebb7c27281ed218d2b94936d005fe45e2c9f759bc16dda0bae8e443a9df690b15a35e815bcdef392a", "lib\/CardDAV\/ContactsManager.php": "e1f8ea154a12c38519bef017afc9e5da08f5df76eed5094a5ee82dfbcb89057573757c94d0d2c6963bb957d91561562e4d8e75db104891bc8789f2d89467bf0f", "lib\/CardDAV\/Converter.php": "866a4182f046765c8a6d09f0384cabe9b2f11685f55751df581d12f08631b2d063baeabadda246a0be1fe9bf486a185c4fa0dd72a623fe7b98d628db91b002c3", "lib\/CardDAV\/ImageExportPlugin.php": "ba46b394bdea11094e86bc60ea5ace0dc388ae47b10a895e5b9d48334b9ae2b4035fe1599c988a2dab6c4af5b3b2bb43d7a6479f1327e62109e731237a0ac6e0", "lib\/CardDAV\/PhotoCache.php": "65bf79d031d68d071b0b2045336b34d0a1b5871980de78bb0625ee7f28c58d7058a62b2b5879273c0acdfd5ef4af1b326eb1b4dcc4b51bae3278ea0a38eb7d30", "lib\/CardDAV\/Plugin.php": "ac5594f69e41c4047213f54a5e040cd5c9c8143fa2143f29b04497817998fffcf9ee310a8bb09427d0803b6d9b98ea7ce67a2c8d841db8c62aa6a18fb327eb18", "lib\/CardDAV\/SyncJob.php": "cfff95660b8cc77d83914eed890f63015b096f949a8cd44b7127a419e986b2c3289d065f9f7ef35f8aa4579d33257df553265e4046d1c3181e71c39dec9df5ef", "lib\/CardDAV\/SyncService.php": "8a8b31a59efb833f6d833df9cdb90308a93baedefa90f2d87c41fc2ca8f13591f7d8d47b3522d86128858ebc66f6b56a5528c4bd043932ad8d0f6c3dd7bbf112", "lib\/CardDAV\/UserAddressBooks.php": "42a21e29a97278d92bace214fbfb409e93d99be27ffbf882d4ca087951ace3775c0d04fd1c68c7c365a12a8dff4f5d7130481875301a506179fe6bbe8de569d9", "lib\/CardDAV\/Xml\/Groups.php": "5b520605a706e447baecc1475a0a75af065608ed3b425f573cc6b69c48ed66f4ee15add433b4b268933c3ef38ede56230c787b616324d221ceca1422408e2c2c", "lib\/Command\/CreateAddressBook.php": "dba8d69396916d64ec82bbe55097dac66dcfd16b2ca38d974b929e05af1a79f31038c9290e9940e0fb87a583b1882a0828f4465417a8d4db0be93a7a0f0b5d82", "lib\/Command\/CreateCalendar.php": "6ecfee333d2ab7e7c6a847981678c03a740429ee4e60cbdaeec765c70393c46ef76dd29efc98efc309e87479b41b21748fe2ec17f85cc7dc80914ce5ea374f93", "lib\/Command\/SyncBirthdayCalendar.php": "ef4fda66a16b91502f0ec82d1a0b39a0bf70cf56622339289b3bd6f6b888e7cb8fc76c9d95a2b32164361af273844f4b428b81731a8c9926a0da47d1cfcc6c21", "lib\/Command\/SyncSystemAddressBook.php": "8222d73232ecd957e06c9681c6b4f7a76ba76c4e66b8a1705bac5978c5d7e6b978a79d3eedb994ad9ed71e7751c5944dd268ecd28c0c240bfd58f79be44f5ba6", "lib\/Comments\/CommentNode.php": "7ba5f42c311251a4b66910df6d42eb834c9c96cc04f35f66fbf50fe67466398dcbd50400c5198f99bb3e94f8fc0abd10f60d68e41088f7c96f8ad80764b27019", "lib\/Comments\/CommentsPlugin.php": "b16f05d2ab88690b45a7880376a612b97eaa24703db4414ae787d03dabbe45f921707092ee93f7584a0dd0155d60aeb019f7ea1f954979a51cabd54e7db9d96f", "lib\/Comments\/EntityCollection.php": "5eba0e0d1bd9ad7112ffe484eab0286d0e33c42ab1e0af7f1adbc11b4c79f8d73254bbe858dafce2fe07f2d190de481388b60c26ab203164559e3595eed545d0", "lib\/Comments\/EntityTypeCollection.php": "2fde6b1baeff6fd7b5fa59cf5845468c9f59e162508637d9ef1db8091b2fafe3be5054220659237e7fb876b69bf0a4c18308ea387756b7665d98779ab8027ed4", "lib\/Comments\/RootCollection.php": "ddd8d4d7f914575e0e3c7bf622d5db0893c7cbfbe40184be30b7f8e0aa302c649c008f6e5bbbe03ab4ccac4990e8809239d9f3b80c133a89cd37fdca9a61ce29", "lib\/Connector\/LegacyDAVACL.php": "9aa2590832385bd37ae662b24323dec3f333002f49042637935e2ad5a4624145ff0bf7bfa05132e0e5511393d3ea198f77ee99ff64a135cb69f5c9f530a98239", "lib\/Connector\/PublicAuth.php": "21e5ffa45890c9ed44d29141869aa535906d5984a05e8b630d6aff76a62a9b8e46ead933d027a54ee7ab44928c4f3ac1b27581b700f692adf83ded360c1314b0", "lib\/Connector\/Sabre\/AppEnabledPlugin.php": "382268083f0de9e1278a1234c15677ff907d0d4c76593f9113de2f85564b68d494666e266656ad2936db3be15a9492882ff898da7c3780d11835dbdc2c26b756", "lib\/Connector\/Sabre\/Auth.php": "a1080d0de54099911bf7c11a8f65e75f53b94426a411d343973c6e6430a80dd05d9d1a2811303ca4d0d0281c4d8486c273cf8430f82a9149bad2ee632b34b089", "lib\/Connector\/Sabre\/BearerAuth.php": "eaad89db70eef392079f6e49fe1fccde9ab0c1f560854123f3036e6a6d65ba4df046806715459b863ca66436f0c3d856c07cc980a551d2e083f9a9a1c76accdd", "lib\/Connector\/Sabre\/BlockLegacyClientPlugin.php": "736e38053df92c96267d22976c668deac5e390c8b0b6d9851fec9807443da6a96fed39956af5b6a701f27e91266924a2b6b8a9a774f628ade2bcd41bdf4b772a", "lib\/Connector\/Sabre\/ChecksumList.php": "f5fc11068799f2d642074ee8225a8e8e44d38695a892124c16527768eb9d56e17def518d0692ed0c1ca75ea3cc0843cbd1b79f33ad97175e888048c787ce38b0", "lib\/Connector\/Sabre\/CommentPropertiesPlugin.php": "169580769fb817df533ca02b1c5504062b2bbd38b15db14c4c58a4d1b5c297fb0f764377fd926d9eaaa21b01b261d2aad7f8970f9d4e972a921d5ba501963228", "lib\/Connector\/Sabre\/CopyEtagHeaderPlugin.php": "8dc4f9e386be5d2b6ee78f6e57a5530d106feccde8ecc327bc9f45885e14411d5441d5bb36bb8698abef2372cf2d59935e326f1726b06d35a0624d834f25acae", "lib\/Connector\/Sabre\/CustomPropertiesBackend.php": "22882c29c106a80394a224716594d0f245ab574e82d6827aff95a9c9bece640cc3d95b882fdb0453ed023bb823140057f01528efb1a036b741224676aaff74f5", "lib\/Connector\/Sabre\/DavAclPlugin.php": "8a0c52670765c5bc9f9e695d47065184937326110801f8839ad34aa57beb0644d0061d2a2a2d6da2714af702e6dcc5bc5f312a49af98f01f0f583d63d5b705ca", "lib\/Connector\/Sabre\/Directory.php": "11b3dfee264253b87eb8ebeb61b00a85957b53d0e61062c4555442dd093daf3da747ee2997e2ebf97ce927f46e696c1db7623558bd3efca913b41d418a9aeecf", "lib\/Connector\/Sabre\/DummyGetResponsePlugin.php": "1e26c580f496eaf2c059a923e68147241f97030179a2c28a4044229f2670f2a565eacc8acb0686284c91af6f0a5fd5b35ae510004bd109f66fbb942061e2de44", "lib\/Connector\/Sabre\/Exception\/EntityTooLarge.php": "7c655476d9b5fd53f85ed27d90479af7f6e03ecf33b724962ac7b5c31a4ad5ca7019c51e5cbf489dbde23c9ada0bc18b29189a00a376464e7409c122b9d062a6", "lib\/Connector\/Sabre\/Exception\/FileLocked.php": "741d2b87c98f38fea1a90e72beaffa7e0ba927e3636dcf51d76f61a2afdacbdb2ddee65ab4c7da8439f92cb78b1ca5091509b0da7197819674e1883500ada498", "lib\/Connector\/Sabre\/Exception\/Forbidden.php": "a04131a7cdfae5f506a2204b41c779a0d7e2b3004e6be9005ead07b00524fe9f6403502a370d582247a1c6b19a9a1fc84002994a5ebda683bfdb97be363458dd", "lib\/Connector\/Sabre\/Exception\/InvalidPath.php": "355360b3fb273b485e3107e7380d2498cfbd971959499c861ca288d9bc4fe6d94e39ae47f4ae06f6d8efad49ba64cde8be617a5556a23415dd79ad77162e7ac3", "lib\/Connector\/Sabre\/Exception\/PasswordLoginForbidden.php": "732d928236b20ecf0308ab9709d030fc4cee5c6c0e5ab26aa262f55cf3e5d38a344e7c78a43ec7fb9fcad668b41b6fd6f2f80438e9d797f3410abb13a76f7792", "lib\/Connector\/Sabre\/Exception\/UnsupportedMediaType.php": "8d5157318f54a09f93fdff269fcee96fe10d53af2e59e9b054061db3442617098cb91b0dfea4775e324e5a2436b8042b37a2b8ac8836ca7d6fb9b122f33a6f6e", "lib\/Connector\/Sabre\/ExceptionLoggerPlugin.php": "5aa3f1a97b56463deff83b756efe9e786d57c0fd8a5b5deef18fdca2a80bff680c3ab2f08fd55eab0cebd21fd167715e5b4766b41c6c6e41b56808439d68b797", "lib\/Connector\/Sabre\/FakeLockerPlugin.php": "23af9d2776f033ac5f769e8961fa31764a92cd110418955d6d598246d1cf52b266b5c3f785104348f1d209fb1d490de2900742b36fe1f5063602c6ef26efac08", "lib\/Connector\/Sabre\/File.php": "768a4e09f7fd6def2a0a7782c33578be6825942c36c1e6674a2dd63ac61fb334bd7c0e079383c24a6cdf4f020065431122a0d835f58fb40fd8da07b181cb6f2e", "lib\/Connector\/Sabre\/FilesPlugin.php": "99bc934fe7da1da0d3f68203ea266e87cf42038f9525c7dec82c0fe1f88ec15d81ed4b59e85ff76741438a8e36824a7277b06a8c9eff340214b6dcb5bdbc5c64", "lib\/Connector\/Sabre\/FilesReportPlugin.php": "628a589e17f374caaac0ea5696f85c13cbc417f0145d0a38d1e0c67293e2bcd90d335570a6de85c40b84203635bf737c02be69140c8531e060bd106756f457fe", "lib\/Connector\/Sabre\/LockPlugin.php": "eca9ec7be65e49563db2cc0080a29635f0c7911311cfbac7adb335be98d6f3d2d5177456c8a610434be5755eda071cd8560b602fdda730f766841cf07fff0b40", "lib\/Connector\/Sabre\/MaintenancePlugin.php": "d31aa6c4fa52f7949e12f8d8b4c7b8885354d027c5d4cf779c9070977908302e65dac2b0c705e54a02ad07bda79ed6a12d0cc46d4b69374e24920947a5de97c1", "lib\/Connector\/Sabre\/Node.php": "93c97a75c3e5b7f26339c037284b34e8548a5a8e97629b94d09db2cf63e0c9af881a3065911eb2c739c4e6cfeaaffa87e5daf124eb7cdcc6e9b0c165527dae4e", "lib\/Connector\/Sabre\/ObjectTree.php": "a0fb62dec322791d76bb3bef3163ac032a134afe7e59a4c94a1e0e93ba18e5c4c47cd602017db53e37f19ac65ee822d44689bc07cf52445f028cc02a4f7b3245", "lib\/Connector\/Sabre\/Principal.php": "e056fad7e34d71d59c9947ebca9f90040e7166bfbaab8afe38fd943cc644bccb33f353a5b0d7325ac7c12ff208c0ca857ffcc5277cd0378419f759c28191ccbe", "lib\/Connector\/Sabre\/QuotaPlugin.php": "de96252f78a900792db0c5882796f87e48a6ea87c36e333170cd9cf3a3f36518ee5cee89f6dfefb64a47b96ecfd2fdec17dd84eb4e462f230afb23f3ea98760e", "lib\/Connector\/Sabre\/Server.php": "ac150587be2dd6a47f474bb7cd794f8305239edbc09ca66c3e286c009f3b686707a4bd4d013e68d411e0e03f7a284fda63360b40d7432eb9835498b868260fa1", "lib\/Connector\/Sabre\/ServerFactory.php": "b2fac014aedb756e56c0188e32671e1c39bcaf7f3142fd6e54609ce71fb067097450b97fe0306518f9989fdd6e49c4a0d36e8cb5e60307e96ce70465c7c49705", "lib\/Connector\/Sabre\/ShareTypeList.php": "d7a1e7318b397a1f144d6df4037004694d4146793f82c356a52ca54716abd037b59fb68e6b8fb103b39ebcf11f4981b8208585160fad8166e7154fd387b3187b", "lib\/Connector\/Sabre\/SharesPlugin.php": "8cde26c26010e21277eb8de40d8da65a1cf730fcaec44ee2b4da7ff177d838bbc9478642084233069bf521428d8d5db9a8f428a5cc20f69559f40e6538333c48", "lib\/Connector\/Sabre\/TagList.php": "1a47e8a7fcc8f8ec8a62440fd70d3c0bdec72e282c0e5dcdc5edaee1347943d75b10cab540e5bb1408a177c1d377872207672f5a8d7567e5c2088f0a568977ad", "lib\/Connector\/Sabre\/TagsPlugin.php": "1361692a781573b46e180e0e9766e4299fb763279823f4f13f12a16ff063de91172e6762ac0f7369e2f67c2df23b3a32987acf159a799e7100b7df6d64e91faa", "lib\/DAV\/CustomPropertiesBackend.php": "b97d59e4c1ee8c952c28eb6728e22730d747d1f498c78b45a62fc2a0e20edf60c94e24f8195819e644e361e4a455d89270310b6c4848ddb88f6fbeda51f197a1", "lib\/DAV\/GroupPrincipalBackend.php": "4ef9f100fbed487dff37482f42b491767233a93f20448837eb8344d357b2afec5cc04d4280feda0f72dc79055f20c1dcabd0c3b7c2ec6e21c7c22e9b6dc6c479", "lib\/DAV\/PublicAuth.php": "8ee81b0190b7ae0115ed6c14cce24b5e0e1c60b226bf1d14fe53afee39aae4de1e1bbf5ece3b1c3074527c8b783691d5d77e685a9da8a30bc435de64481f59bc", "lib\/DAV\/Sharing\/Backend.php": "f58c6c98d22096197c635f17141b756fb9093a46e5dd64403f167ebffb15eafa844b6414f6512642bb2e9fee8b511e6b220d5a59a349f3dd99a3beb3d70a4fc0", "lib\/DAV\/Sharing\/IShareable.php": "9ca2c3ad9e89f8ac3b28b4d69376953d669bef62439b201c87fb0e0dc15906c6fff7926470e5c82c459f1fdddee9ed52274428d469315a0bf8d1a1dbd47c3472", "lib\/DAV\/Sharing\/Plugin.php": "15e554ab3b7db6ae75c6549d576fd4085c1f06ab8f36e3ae684c72b340e138b8c0649b64f7218035f6de6cfee9ac5820278bdf8641c2baf69030b5c83aeca760", "lib\/DAV\/Sharing\/Xml\/Invite.php": "97844c5c54313fcd995434def78678fcc179f25de99ac141331ed3d2dbf51f63d89393ff441712b37c7591df956b11c0090a840094082a1d3217a924dc70ec16", "lib\/DAV\/Sharing\/Xml\/ShareRequest.php": "7402563e32686947f901a7ed8d1085b8e87d281b8a716e53f9886056550c4ed33805ced612e5c38681255b654022a5989bc17ca93b4106835ade84b01ac045d9", "lib\/DAV\/SystemPrincipalBackend.php": "60d73cb4ae8da3e31b49f26612bb11d767a4d284145ad946a0df2d588b8c9d2a36d2fec99cd42b37c8d20e09311d918db1879d2c115353d2ef0de02ed20e6483", "lib\/Files\/BrowserErrorPagePlugin.php": "9e6225ca96593e86bda612bb53fd3ce96d0d75ed98aa2becc16c5f8ebcb554b23bda1ee306b243f49c44fce71c2ecd17e7d3548c6dd85bba4984fbccefd3f022", "lib\/Files\/FileSearchBackend.php": "0cadc3da8d99a8bde93c646a2836839323ccb50a759e529383be9a267fbb7aa83b2be7c1bc92cafbd646204be1e582d273de4de701ce8abee65257487c33d44d", "lib\/Files\/FilesHome.php": "3af43175383a67019bdd3240219640a8e475c286cfef814b72902519e1e5c635af33547cda0610a61983775545059f11f9f44853fc5d41a52fd9e8cfce3193a4", "lib\/Files\/RootCollection.php": "6022ac72bc6908b010da7af411c8a160f5a28b2ac01ec52c8ebd2e0377678660766f4e64771b154fe07a2d13fcce5c28113e9e4acb966ee81d1c8e019c6e0b8b", "lib\/Files\/Sharing\/FilesDropPlugin.php": "7472b94caf5bac323506045eff63d782629ae70be5d0f6b5ba84abdaa34f6b32ce8220cc768790b591f2b37a20eedeccc4cc36c350564c81f8e53a6498b0851a", "lib\/Files\/Sharing\/PublicLinkCheckPlugin.php": "1b0bf44f59d14aeffaef8aa2287f35cb5cb201a8ea2f68f5fc5ef998257a5fbc0951708bbe2e31ba9e581d3396db2aab252a4e74a09acd7e964fcf07386aa585", "lib\/HookManager.php": "1fafb2f9ddbed077d8ae26acb4b997bad6f81d3354b83e35a0bfee398e1f7c40b3395aa4623ececc24b2432c8ccaca2e55e49433d3fe82663ccf2e9c1bab61ee", "lib\/Migration\/BuildCalendarSearchIndex.php": "97a61f4766a2b0b120e1dc97e31688c0fdacdc05b0e87f6dd85a80c51217ffeb0c900a0ad011f1764f25104f7590273f3d5eeb2b545f01406a73077910b145b5", "lib\/Migration\/BuildCalendarSearchIndexBackgroundJob.php": "edcc16a6fde2a0e71545e439d812ca35d103fd7f34f224096f5ead7bc3527e9f90bf879d074a31bf741764232769e388a922e587226011a9b7add9ad3d404729", "lib\/Migration\/CalDAVRemoveEmptyValue.php": "1706022ab0e05f12e47fe79513ebf472b21af56c4b96f3634ebaabd8dae108759842b1d791bdfa71a60a2311e3206832d82a6c5abe255478da20deba2e09e817", "lib\/Migration\/FixBirthdayCalendarComponent.php": "71cbdc9f969b70f8c288c129bfbe0115bbc03ff461d9274586c62199b8e2da7a28b72c9c14710a6fc1e896dd975e1fe7dd170b46b5bc6d70f6fc8ed96d00a83a", "lib\/RootCollection.php": "1fa337740dacf8c47b480c3e1e4a3c6c56a0a1d8f529260df52487ad31b1af9d9fd145bc23c48a5dfc997712610f05b08fadce6cfc2da82d1807e40099b8bc91", "lib\/Server.php": "1620aa151ed7f20795b78cf0e85eface6e28823e4cd944859162ac659b8204c15b88a01bff82acba2b0b9309df5826b94c2024243bcb36d74b19e4e7c5a8491c", "lib\/SystemTag\/SystemTagMappingNode.php": "74154b72663fecdfd4fa1d0a0b0ee1611250084e8a15a22c467d873b945204690f934696ab904855497107eabb00767cee43fe73b4d6e5b6dd427526d34375c3", "lib\/SystemTag\/SystemTagNode.php": "df23734e7b481841782b3ce60d1d65b5d24af040d076583ad3ed4946b30eb6065be5fcd37ad5a697fdd87e74a0e00874dd36933f4b693bded4299674e46e65b5", "lib\/SystemTag\/SystemTagPlugin.php": "36e52211ffc8ab97da480914fb9c50d40a5cdcd3ac1934073811346ee95f32962a7a11e68173a0d5df3835cc79a2860951135abcacef12739b01f43ab351e43d", "lib\/SystemTag\/SystemTagsByIdCollection.php": "9f203cee9c2710d250c39f9214396dc5e938c75ff7de9636db435dd9594f7e4b01806b6241e45c3796dc6e2b97102f2acfd0d7116138e8593b1e26fa60af04c9", "lib\/SystemTag\/SystemTagsObjectMappingCollection.php": "5d368ee2acf16b44d939942c44b3e156576e163a240ae9370daa27a8ca0a3532aac00cc51b34f047c22eca8437ff69687b0f7afe25a8ce79b65514c2aa2bb05d", "lib\/SystemTag\/SystemTagsObjectTypeCollection.php": "3e09064aa863ccb588d347f6deba3832db52d1e1c09d6f3b03b9d80dfb4b4ce2c6128144453c7fe0b0d9a098bbfcbb9e6112555bdd765c93af2782d3d5183db1", "lib\/SystemTag\/SystemTagsRelationsCollection.php": "d9a1973996c83fb6816ca71149ba4bf6cf1eb07d6487557874e394241c9ba0de416371191234e9ff947c26f45cefa911eec020d983386585a7fc08fd8a742616", "lib\/Upload\/AssemblyStream.php": "84c3c5b54fa6408dfa6665deff156718ab38c8ff81f71aa8dd130e5a44f7ab38e822ca419e08859ce0a131ecd3d2a116d98e9608cd9b28ce442f9a8370bd6e16", "lib\/Upload\/FutureFile.php": "843dab4fc79263e8601ff575461e58282a94b42755f9e93a9815913b97555e66b9c617db5bb6920b5f041bc50144ceb4e9ee2325453bdd9f6507d45a09b08a4e", "lib\/Upload\/RootCollection.php": "6bd4e006688cbff7708b64bd052a1673ab7d5654285bf31e2da60a742907dc2fe66c4c3e44075604a033819f29e6844b8eeba0bb8a682c14df31d049f1d37839", "lib\/Upload\/UploadFolder.php": "7daf72953a79fd49fc5c41b416b1f27640b5a324627944d9177ffd6d08e3c73f79304d19f2765e5985b4ad54ade0c98fa053e2e5ec1f3738bbd8845ee5e6ac2f", "lib\/Upload\/UploadHome.php": "7d1b622b1201e83179af4f40488b9a54cc30911767d391c5b6fb498d0da85b809098cd61a4bbcffa2430a09aaf4970c9c7b18ada218c76781828e09d95a9af0b", "templates\/exception.php": "f2f5e3a99ac8f8d4766d5c6b6e960026ccac6aa914102dcf33730be0667615b3d2ccd6668f7595a787bdd818ff438e03417ddc2566b059b2b5c971ad5cdc490a" }, "signature": "wdYul4FuG8zmg7JGXEQmi\/c2ZkZPSZRUc24BNOp3iXbR0s3aU5r6cvmhpDhbtfPZ\/Zf7bhu9Boykw4YkqZFCcRg43tAt78OsBEsgA9e+fSbuZptd\/SJS1yUaElAIE23OkZCD1\/ALWfonGfVV3V25\/vpxB37yhV5tr8ZDYvNV4gbsxFygr9uIVkJw8Odo0lrN\/dqww5YmURgwUbZSKNk2QrxDbFZt7AcFVC\/0MO94MJ\/maAKBilkQ2uC\/0vtLKy1W6Xj\/TrpqqP03OJkIzFt3\/x1NSIKJ4CIxBQzqYpnck2gh1RnVRcTb5nHyFAhqPUSuGsOdL44HLb7SLcyMbBdciw==", "certificate": "-----BEGIN CERTIFICATE-----\r\nMIIEojCCA4qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwezELMAkGA1UEBhMCREUx\r\nGzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzEXMBUGA1UECgwOTmV4dGNsb3Vk\r\nIEdtYkgxNjA0BgNVBAMMLU5leHRjbG91ZCBDb2RlIFNpZ25pbmcgSW50ZXJtZWRp\r\nYXRlIEF1dGhvcml0eTAeFw0xNjA2MTIyMTA1MDZaFw00MTA2MDYyMTA1MDZaMGYx\r\nCzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxEjAQBgNV\r\nBAcMCVN0dXR0Z2FydDEXMBUGA1UECgwOTmV4dGNsb3VkIEdtYkgxDTALBgNVBAMM\r\nBGNvcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUxcrn2DC892IX\r\n8+dJjZVh9YeHF65n2ha886oeAizOuHBdWBfzqt+GoUYTOjqZF93HZMcwy0P+xyCf\r\nQqak5Ke9dybN06RXUuGP45k9UYBp03qzlUzCDalrkj+Jd30LqcSC1sjRTsfuhc+u\r\nvH1IBuBnf7SMUJUcoEffbmmpAPlEcLHxlUGlGnz0q1e8UFzjbEFj3JucMO4ys35F\r\nqZS4dhvCngQhRW3DaMlQLXEUL9k3kFV+BzlkPzVZEtSmk4HJujFCnZj1vMcjQBg\/\r\nBqq1HCmUB6tulnGcxUzt\/Z\/oSIgnuGyENeke077W3EyryINL7EIyD4Xp7sxLizTM\r\nFCFCjjH1AgMBAAGjggFDMIIBPzAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIG\r\nQDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRp\r\nZmljYXRlMB0GA1UdDgQWBBQwc1H9AL8pRlW2e5SLCfPPqtqc0DCBpQYDVR0jBIGd\r\nMIGagBRt6m6qqTcsPIktFz79Ru7DnnjtdKF+pHwwejELMAkGA1UEBhMCREUxGzAZ\r\nBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzESMBAGA1UEBwwJU3R1dHRnYXJ0MRcw\r\nFQYDVQQKDA5OZXh0Y2xvdWQgR21iSDEhMB8GA1UEAwwYTmV4dGNsb3VkIFJvb3Qg\r\nQXV0aG9yaXR5ggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH\r\nAwEwDQYJKoZIhvcNAQELBQADggEBADZ6+HV\/+0NEH3nahTBFxO6nKyR\/VWigACH0\r\nnaV0ecTcoQwDjKDNNFr+4S1WlHdwITlnNabC7v9rZ\/6QvbkrOTuO9fOR6azp1EwW\r\n2pixWqj0Sb9\/dSIVRpSq+jpBE6JAiX44dSR7zoBxRB8DgVO2Afy0s80xEpr5JAzb\r\nNYuPS7M5UHdAv2dr16fDcDIvn+vk92KpNh1NTeZFjBbRVQ9DXrgkRGW34TK8uSLI\r\nYG6jnfJ6eJgTaO431ywWPXNg1mUMaT\/+QBOgB299QVCKQU+lcZWptQt+RdsJUm46\r\nNY\/nARy4Oi4uOe88SuWITj9KhrFmEvrUlgM8FvoXA1ldrR7KiEg=\r\n-----END CERTIFICATE-----" } appinfo/database.xml 0000604 00000041126 15247100404 0010463 0 ustar 00 <?xml version="1.0" encoding="ISO-8859-1" ?> <database> <!-- CREATE TABLE addressbooks ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, principaluri VARBINARY(255), displayname VARCHAR(255), uri VARBINARY(200), description TEXT, synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', UNIQUE(principaluri(100), uri(100)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*addressbooks</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>principaluri</name> <type>text</type> <length>255</length> </field> <field> <name>displayname</name> <type>text</type> <length>255</length> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>description</name> <type>text</type> <length>255</length> </field> <field> <name>synctoken</name> <type>integer</type> <default>1</default> <notnull>true</notnull> <unsigned>true</unsigned> </field> <index> <name>addressbook_index</name> <unique>true</unique> <field> <name>principaluri</name> </field> <field> <name>uri</name> </field> </index> </declaration> </table> <!-- CREATE TABLE cards ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, addressbookid INT(11) UNSIGNED NOT NULL, carddata MEDIUMBLOB, uri VARBINARY(200), lastmodified INT(11) UNSIGNED, etag VARBINARY(32), size INT(11) UNSIGNED NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*cards</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>addressbookid</name> <type>integer</type> <default>0</default> <notnull>true</notnull> </field> <field> <name>carddata</name> <type>blob</type> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>lastmodified</name> <type>integer</type> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>etag</name> <type>text</type> <length>32</length> </field> <field> <name>size</name> <type>integer</type> <notnull>true</notnull> <unsigned>true</unsigned> <length>11</length> </field> </declaration> </table> <!-- CREATE TABLE addressbookchanges ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, uri VARBINARY(200) NOT NULL, synctoken INT(11) UNSIGNED NOT NULL, addressbookid INT(11) UNSIGNED NOT NULL, operation TINYINT(1) NOT NULL, INDEX addressbookid_synctoken (addressbookid, synctoken) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*addressbookchanges</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>synctoken</name> <type>integer</type> <default>1</default> <notnull>true</notnull> <unsigned>true</unsigned> </field> <field> <name>addressbookid</name> <type>integer</type> <notnull>true</notnull> </field> <field> <name>operation</name> <type>integer</type> <notnull>true</notnull> <length>1</length> </field> <index> <name>addressbookid_synctoken</name> <field> <name>addressbookid</name> </field> <field> <name>synctoken</name> </field> </index> </declaration> </table> <!-- CREATE TABLE calendarobjects ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, calendardata MEDIUMBLOB, uri VARBINARY(200), calendarid INTEGER UNSIGNED NOT NULL, lastmodified INT(11) UNSIGNED, etag VARBINARY(32), size INT(11) UNSIGNED NOT NULL, componenttype VARBINARY(8), firstoccurence INT(11) UNSIGNED, lastoccurence INT(11) UNSIGNED, uid VARBINARY(200), UNIQUE(calendarid, uri) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*calendarobjects</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>calendardata</name> <type>blob</type> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>calendarid</name> <type>integer</type> <unsigned>true</unsigned> <notnull>true</notnull> </field> <field> <name>lastmodified</name> <type>integer</type> <unsigned>true</unsigned> </field> <field> <name>etag</name> <type>text</type> <length>32</length> </field> <field> <name>size</name> <type>integer</type> <notnull>true</notnull> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>componenttype</name> <type>text</type> <length>8</length> </field> <field> <name>firstoccurence</name> <type>integer</type> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>lastoccurence</name> <type>integer</type> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>uid</name> <type>text</type> <length>255</length> </field> <field> <comments>0 - public, 1 - private, 2 - confidential</comments> <name>classification</name> <type>integer</type> <default>0</default> </field> <index> <name>calobjects_index</name> <unique>true</unique> <field> <name>calendarid</name> </field> <field> <name>uri</name> </field> </index> </declaration> </table> <!-- CREATE TABLE calendars ( id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, principaluri VARBINARY(100), displayname VARCHAR(100), uri VARBINARY(200), synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1', description TEXT, calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', calendarcolor VARBINARY(10), timezone CLOB, components VARBINARY(20), transparent TINYINT(1) NOT NULL DEFAULT '0', UNIQUE(principaluri, uri) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*calendars</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>principaluri</name> <type>text</type> <length>255</length> </field> <field> <name>displayname</name> <type>text</type> <length>255</length> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>synctoken</name> <type>integer</type> <default>1</default> <notnull>true</notnull> <unsigned>true</unsigned> </field> <field> <name>description</name> <type>text</type> <length>255</length> </field> <field> <name>calendarorder</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <unsigned>true</unsigned> </field> <field> <name>calendarcolor</name> <type>text</type> </field> <field> <name>timezone</name> <type>clob</type> </field> <field> <name>components</name> <type>text</type> <length>64</length> </field> <field> <name>transparent</name> <type>integer</type> <length>1</length> <notnull>true</notnull> <default>0</default> </field> <index> <name>calendars_index</name> <unique>true</unique> <field> <name>principaluri</name> </field> <field> <name>uri</name> </field> </index> </declaration> </table> <!-- CREATE TABLE calendarchanges ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, uri VARBINARY(200) NOT NULL, synctoken INT(11) UNSIGNED NOT NULL, calendarid INT(11) UNSIGNED NOT NULL, operation TINYINT(1) NOT NULL, INDEX calendarid_synctoken (calendarid, synctoken) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*calendarchanges</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>synctoken</name> <type>integer</type> <default>1</default> <notnull>true</notnull> <unsigned>true</unsigned> </field> <field> <name>calendarid</name> <type>integer</type> <notnull>true</notnull> </field> <field> <name>operation</name> <type>integer</type> <notnull>true</notnull> <length>1</length> </field> <index> <name>calendarid_synctoken</name> <field> <name>calendarid</name> </field> <field> <name>synctoken</name> </field> </index> </declaration> </table> <!-- CREATE TABLE calendarsubscriptions ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, uri VARBINARY(200) NOT NULL, principaluri VARBINARY(100) NOT NULL, source TEXT, displayname VARCHAR(100), refreshrate VARCHAR(10), calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', calendarcolor VARBINARY(10), striptodos TINYINT(1) NULL, stripalarms TINYINT(1) NULL, stripattachments TINYINT(1) NULL, lastmodified INT(11) UNSIGNED, UNIQUE(principaluri, uri) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*calendarsubscriptions</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>uri</name> <type>text</type> </field> <field> <name>principaluri</name> <type>text</type> <length>255</length> </field> <field> <name>source</name> <type>text</type> <length>255</length> </field> <field> <name>displayname</name> <type>text</type> <length>100</length> </field> <field> <name>refreshrate</name> <type>text</type> <length>10</length> </field> <field> <name>calendarorder</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <unsigned>true</unsigned> </field> <field> <name>calendarcolor</name> <type>text</type> </field> <field> <name>striptodos</name> <type>integer</type> <length>1</length> </field> <field> <name>stripalarms</name> <type>integer</type> <length>1</length> </field> <field> <name>stripattachments</name> <type>integer</type> <length>1</length> </field> <field> <name>lastmodified</name> <type>integer</type> <unsigned>true</unsigned> </field> <index> <name>calsub_index</name> <unique>true</unique> <field> <name>principaluri</name> </field> <field> <name>uri</name> </field> </index> </declaration> </table> <!-- CREATE TABLE schedulingobjects ( id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, principaluri VARBINARY(255), calendardata MEDIUMBLOB, uri VARBINARY(200), lastmodified INT(11) UNSIGNED, etag VARBINARY(32), size INT(11) UNSIGNED NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; --> <table> <name>*dbprefix*schedulingobjects</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>principaluri</name> <type>text</type> <length>255</length> </field> <field> <name>calendardata</name> <type>blob</type> </field> <field> <name>uri</name> <type>text</type> <length>255</length> </field> <field> <name>lastmodified</name> <type>integer</type> <unsigned>true</unsigned> </field> <field> <name>etag</name> <type>text</type> <length>32</length> </field> <field> <name>size</name> <type>integer</type> <notnull>true</notnull> <unsigned>true</unsigned> <length>11</length> </field> </declaration> </table> <table> <name>*dbprefix*cards_properties</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>addressbookid</name> <type>integer</type> <default></default> <notnull>true</notnull> <length>11</length> </field> <field> <name>cardid</name> <type>integer</type> <default></default> <notnull>true</notnull> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>name</name> <type>text</type> <default></default> <notnull>false</notnull> <length>64</length> </field> <field> <name>value</name> <type>text</type> <default></default> <notnull>false</notnull> <length>255</length> </field> <field> <name>preferred</name> <type>integer</type> <default>1</default> <notnull>true</notnull> <length>4</length> </field> <index> <name>card_contactid_index</name> <field> <name>cardid</name> <sorting>ascending</sorting> </field> </index> <index> <name>card_name_index</name> <field> <name>name</name> <sorting>ascending</sorting> </field> </index> <index> <name>card_value_index</name> <field> <name>value</name> <sorting>ascending</sorting> </field> </index> </declaration> </table> <table> <name>*dbprefix*calendarobjects_props</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>calendarid</name> <type>integer</type> <default></default> <notnull>true</notnull> <length>11</length> </field> <field> <name>objectid</name> <type>integer</type> <default></default> <notnull>true</notnull> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>name</name> <type>text</type> <default></default> <notnull>false</notnull> <length>64</length> </field> <field> <name>parameter</name> <type>text</type> <default></default> <notnull>false</notnull> <length>64</length> </field> <field> <name>value</name> <type>text</type> <default></default> <notnull>false</notnull> <length>255</length> </field> <index> <name>calendarobject_index</name> <field> <name>objectid</name> <sorting>ascending</sorting> </field> </index> <index> <name>calendarobject_name_index</name> <field> <name>name</name> <sorting>ascending</sorting> </field> </index> <index> <name>calendarobject_value_index</name> <field> <name>value</name> <sorting>ascending</sorting> </field> </index> </declaration> </table> <table> <name>*dbprefix*dav_shares</name> <declaration> <field> <name>id</name> <type>integer</type> <default>0</default> <notnull>true</notnull> <autoincrement>1</autoincrement> <unsigned>true</unsigned> <length>11</length> </field> <field> <name>principaluri</name> <type>text</type> <length>255</length> </field> <field> <name>type</name> <type>text</type> <length>255</length> </field> <field> <name>access</name> <type>integer</type> <length>1</length> </field> <field> <name>resourceid</name> <type>integer</type> <notnull>true</notnull> <unsigned>true</unsigned> </field> <field> <name>publicuri</name> <type>text</type> <length>255</length> </field> <index> <name>dav_shares_index</name> <unique>true</unique> <field> <name>principaluri</name> </field> <field> <name>resourceid</name> </field> <field> <name>type</name> </field> <field> <name>publicuri</name> </field> </index> </declaration> </table> </database>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.03 |
proxy
|
phpinfo
|
Settings