Page MenuHomePhorge

No OneTemporary

Size
92 KB
Referenced Files
None
Subscribers
None
diff --git a/plugins/calendar/.gitignore b/plugins/calendar/.gitignore
index 93262bcd..7c2f14c7 100644
--- a/plugins/calendar/.gitignore
+++ b/plugins/calendar/.gitignore
@@ -1,7 +1,8 @@
*.swp
*.bak
*.old
*~
config.inc.php
skins/*
-!skins/default
\ No newline at end of file
+!skins/default
+!skins/larry
\ No newline at end of file
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index 79635bb4..a95d0fcb 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -1,1330 +1,1330 @@
/**
* Roundcube Calendar plugin styles for skin "Larry"
*
* Copyright (c) 2012, The Roundcube Dev Team
* Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*
* $Id$
*/
body.calendarmain {
overflow: hidden;
}
body.calendarmain #mainscreen {
left: 0;
}
#calendarsidebar {
position: absolute;
top: 0;
left: 10px;
bottom: 0;
width: 240px;
}
#datepicker {
margin-top: 12px;
width: 100%;
min-height: 190px;
}
#datepicker .ui-datepicker {
width: 100% !important;
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
}
#datepicker .ui-datepicker td a {
padding: 5px 4px;
font-size: 12px;
}
#datepicker td.ui-datepicker-activerange {
border-color: #69a2b6;
}
#datepicker .ui-datepicker-activerange a {
color: #185d7a;
background: #d9f1fb;
background: -moz-linear-gradient(top, #d9f1fb 0%, #c5e3ee 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d9f1fb), color-stop(100%,#c5e3ee));
background: -o-linear-gradient(top, #d9f1fb 0%, #c5e3ee 100%);
background: -ms-linear-gradient(top, #d9f1fb 0%, #c5e3ee 100%);
background: linear-gradient(top, #d9f1fb 0%, #c5e3ee 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d9f1fb', endColorstr='#c5e3ee', GradientType=0);
}
#datepicker .ui-datepicker-activerange a.ui-state-active {
color: #fff;
background: #00acd4;
background: -moz-linear-gradient(top, #00acd4 0%, #008fc7 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#00acd4), color-stop(100%,#008fc7));
background: -o-linear-gradient(top, #00acd4 0%, #008fc7 100%);
background: -ms-linear-gradient(top, #00acd4 0%, #008fc7 100%);
background: linear-gradient(top, #00acd4 0%, #008fc7 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00acd4', endColorstr='#008fc7', GradientType=0);
}
#datepicker td.ui-datepicker-week-col {
cursor: pointer;
}
#datepicker .ui-datepicker-title {
margin: 2px 2.3em 3px 2.3em;
}
#datepicker .ui-datepicker .ui-datepicker-prev,
#datepicker .ui-datepicker .ui-datepicker-next {
top: 4px;
}
#calendarsidebartoggle {
position: absolute;
left: 254px;
width: 8px;
top: 37px;
bottom: 0;
background: url(images/toggle.gif) 0 48% no-repeat transparent;
cursor: pointer;
}
div.sidebarclosed {
background-position: -8px 48% !important;
}
#calendarsidebartoggle:hover {
background-color: #ddd;
}
#calendar {
position: absolute;
top: 0;
left: 266px;
right: 0;
bottom: 0;
padding-bottom: 28px;
}
.calendarmain #message.statusbar {
border: 1px solid #c3c3c3;
border-bottom-color: #ababab;
}
#print {
width: 680px;
}
pre {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
}
#calendars {
position: absolute;
top: 276px;
left: 0;
bottom: 0;
right: 0;
}
#calendarslist li {
margin: 0;
height: 20px;
padding: 6px 8px 2px;
display: block;
position: relative;
white-space: nowrap;
}
#calendarslist li label {
display: block;
}
#calendarslist li span.calname {
cursor: default;
background: url(images/calendars.png) 0 -2px no-repeat;
padding-left: 22px;
padding-bottom: 2px;
color: #004458;
}
#calendarslist li span.handle {
display: inline-block;
padding: 0;
border-radius: 7px;
margin-right: 6px;
width: 10px;
height: 10px;
font-size: 0.8em;
border: 1px solid rgba(0, 0, 0, 0.5);
-webkit-box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
-moz-box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
}
#calendarslist li input {
position: absolute;
top: 3px;
right: 5px;
}
#calendarslist li.selected {
background-color: #c7e3ef;
}
#calendarslist li.selected span.calname {
font-weight: bold;
}
#calendarslist li.readonly span.calname {
background-position: 0 -20px;
}
#calendarslist li.other span.calname {
background-position: 0 -38px;
}
#calendarslist li.other.readonly span.calname {
background-position: 0 -56px;
}
#calendarslist li.shared span.calname {
background-position: 0 -74px;
}
#calendarslist li.shared.readonly span.calname {
background-position: 0 -92px;
}
#calfeedurl {
width: 98%;
background: #fbfbfb;
padding: 4px;
margin-bottom: 1em;
resize: none;
}
#agendalist {
width: 100%;
margin: 0 auto;
margin-top: 60px;
border: 1px solid #C1DAD7;
display: none;
}
#agendalist table {
width: 100%;
}
#agendalist td,
#agendalist th {
border-right: 1px solid #C1DAD7;
border-bottom: 1px solid #C1DAD7;
background: #fff;
padding: 6px 6px 6px 12px;
}
#agendalist tr {
vertical-align: top;
}
#agendalist th {
font-weight: bold;
}
#calendartoolbar {
position: absolute;
top: -6px;
right: 0;
height: 40px;
z-index: 200;
}
#calendartoolbar a {
padding-right: 10px;
}
#quicksearchbar {
right: 4px;
}
body.calendarmain #searchmenulink {
width: 15px;
}
div.uidialog {
display: none;
}
#user {
position: absolute;
top: 10px;
right: 100px;
left: 100px;
text-align: center;
}
a.morelink {
font-size: 90%;
color: #0069a6;
text-decoration: none;
}
a.morelink:hover {
text-decoration: underline;
}
a.miniColors-trigger {
margin-top: -3px;
}
#attachmentcontainer {
position: absolute;
top: 80px;
left: 20px;
right: 20px;
bottom: 20px;
}
#attachmentframe {
width: 100%;
height: 100%;
border: 1px solid #999999;
background-color: #F9F9F9;
}
#partheader {
position: absolute;
top: 20px;
left: 220px;
right: 20px;
height: 40px;
}
#partheader table td {
padding-left: 2px;
padding-right: 4px;
vertical-align: middle;
font-size: 11px;
}
#partheader table td.title {
color: #666;
font-weight: bold;
}
#edit-attachments {
margin-top: 0.6em;
}
#edit-attachments ul li {
display: block;
color: #333;
font-weight: bold;
padding: 8px 4px 3px 30px;
text-shadow: 0px 1px 1px #fff;
text-decoration: none;
white-space: nowrap;
}
#edit-attachments ul li a.file {
padding: 0;
}
#edit-attachments-form {
padding-top: 1.2em;
}
#edit-attachments-form .buttons {
margin: 0.5em 0;
}
#event-attachments .attachmentslist li {
float: left;
margin-right: 1em;
}
#event-attachments .attachmentslist li a {
outline: none;
}
#event-attendees span.attendee {
padding-right: 18px;
margin-right: 0.5em;
background: url(images/attendee-status.gif) right 0 no-repeat;
}
#event-attendees span.attendee a.mailtolink {
text-decoration: none;
white-space: nowrap;
}
#event-attendees span.attendee a.mailtolink:hover {
text-decoration: underline;
}
#event-attendees span.accepted {
background-position: right -20px;
}
#event-attendees span.declined {
background-position: right -40px;
}
#event-attendees span.tentative {
background-position: right -60px;
}
#event-attendees span.organizer {
background-position: right -80px;
}
/* jQuery UI overrides */
#eventshow h1 {
font-size: 18px;
margin: -0.3em 0 0.4em 0;
}
#eventshow label,
#eventshow h5.label {
font-weight: normal;
font-size: 1em;
color: #999;
margin: 0 0 0.2em 0;
}
#eventshow {
margin: 0 -0.2em;
}
#eventshow.sensitivity-private {
background: url(images/badge_private.png) top right no-repeat;
}
#eventshow.sensitivity-confidential {
background: url(images/badge_confidential.png) top right no-repeat;
}
.sensitivity-private #event-title {
margin-right: 50px;
}
.sensitivity-confidential #event-title {
margin-right: 60px;
}
#eventshow div.event-line {
margin-top: 0.1em;
margin-bottom: 0.3em;
}
#eventedit {
position: relative;
top: -1.5em;
padding: 0.5em 0.1em;
margin: 0 -0.2em;
}
#eventedit input.text,
#eventedit textarea {
width: 97%;
}
#eventtabs {
position: relative;
padding: 0;
border: 0;
border-radius: 0;
}
div.form-section,
#eventshow div.event-section,
#eventtabs div.event-section {
margin-top: 0.2em;
margin-bottom: 0.8em;
}
#eventtabs .border-after {
padding-bottom: 0.8em;
margin-bottom: 0.8em;
border-bottom: 2px solid #fafafa;
}
#eventshow label,
#eventedit label,
.form-section label {
display: inline-block;
min-width: 7em;
padding-right: 0.5em;
}
#eventedit .formtable td.label {
min-width: 6em;
}
td.topalign {
vertical-align: top;
}
#eventedit label.weekday,
#eventedit label.monthday {
min-width: 3em;
}
#eventedit label.month {
min-width: 5em;
}
#edit-recurrence-yearly-bymonthblock {
margin-left: 7.5em;
}
#eventedit .recurrence-form {
display: none;
}
#eventedit .formtable td {
padding: 0.2em 0;
}
.ui-dialog .event-update-confirm {
padding: 0 0.5em 0.5em 0.5em;
}
.event-dialog-message,
.event-update-confirm .message {
margin-top: 0.5em;
padding: 0.8em;
border: 1px solid #ffdf0e;
background-color: #fef893;
}
.event-dialog-message .message,
.event-update-confirm .message {
margin-bottom: 0.5em;
}
.edit-recurring-warning .savemode {
padding-left: 20px;
}
.event-update-confirm .savemode {
padding-left: 30px;
}
.event-dialog-message span.ui-icon,
.event-update-confirm span.ui-icon {
float: left;
margin: 0 7px 20px 0;
}
.event-dialog-message label,
.event-update-confirm label {
min-width: 3em;
padding-right: 1em;
}
.event-update-confirm a.button {
margin: 0 0.5em 0 0.2em;
min-width: 5em;
}
#event-rsvp,
#edit-attendees-notify {
margin: 0.3em 0;
padding: 0.5em;
}
#edit-attendees-table {
width: 100%;
margin-top: 0.5em;
}
#edit-attendees-table td.role {
width: 9em;
}
#edit-attendees-table td.availability,
#edit-attendees-table td.confirmstate {
width: 4em;
}
#edit-attendees-table td.options {
width: 3em;
text-align: right;
padding-right: 4px;
}
#edit-attendees-table td.name {
width: auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#edit-attendees-form {
position: relative;
margin-top: 1em;
}
#edit-attendees-form #edit-attendee-schedule {
position: absolute;
top: 0;
right: 0;
}
#edit-attendees-table select.edit-attendee-role {
border: 0;
padding: 2px;
background: white;
}
.availability img.availabilityicon {
margin: 1px;
width: 14px;
height: 14px;
border-radius: 4px;
-moz-border-radius: 4px;
}
.availability img.availabilityicon.loading {
background: url(images/loading_blue.gif) center no-repeat;
}
#schedule-freebusy-times td.unknown,
.availability img.availabilityicon.unknown {
background: #ddd;
}
#schedule-freebusy-times td.free,
.availability img.availabilityicon.free {
background: #0c0;
}
#schedule-freebusy-times td.busy,
.availability img.availabilityicon.busy {
background: #c00;
}
#schedule-freebusy-times td.tentative,
.availability img.availabilityicon.tentative {
background: #66d;
}
#schedule-freebusy-times td.out-of-office,
.availability img.availabilityicon.out-of-office {
background: #f0b400;
}
#schedule-freebusy-times td.all-busy,
#schedule-freebusy-times td.all-tentative,
#schedule-freebusy-times td.all-out-of-office {
background-image: url(images/freebusy-colors.png);
background-position: top right;
background-repeat: no-repeat;
}
#schedule-freebusy-times td.all-tentative {
background-position: right -40px;
}
#schedule-freebusy-times td.all-out-of-office {
background-position: right -80px;
}
#edit-attendees-legend {
margin-top: 3em;
margin-bottom: 0.5em;
}
#edit-attendees-legend .legend {
margin-right: 2em;
white-space: nowrap;
}
#edit-attendees-legend img.availabilityicon {
vertical-align: middle;
}
#edit-attendees-table tbody td.confirmstate {
overflow: hidden;
white-space: nowrap;
text-indent: -2000%;
}
#edit-attendees-table td.confirmstate span {
display: block;
width: 20px;
background: url(images/attendee-status.gif) 5px 0 no-repeat;
}
#edit-attendees-table td.confirmstate span.needs-action {
}
#edit-attendees-table td.confirmstate span.accepted {
background-position: 5px -20px;
}
#edit-attendees-table td.confirmstate span.declined {
background-position: 5px -40px;
}
#edit-attendees-table td.confirmstate span.tentative {
background-position: 5px -60px;
}
#attendees-freebusy-table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
margin: 0.5em 0;
}
#attendees-freebusy-table td.attendees {
width: 18em;
border: 1px solid #ccc;
vertical-align: top;
overflow: hidden;
}
#attendees-freebusy-table td.times {
width: auto;
vertical-align: top;
border: 1px solid #ccc;
}
#attendees-freebusy-table div.scroll {
position: relative;
overflow: auto;
}
#attendees-freebusy-table h3.boxtitle {
margin: 0;
height: auto !important;
border-color: #ccc;
}
.attendees-list .attendee {
padding: 3px 4px 3px 1px;
background: url(images/attendee-status.gif) 2px -97px no-repeat;
white-space: nowrap;
}
.attendees-list a.attendee-role-toggle {
display: inline-block;
width: 16px;
margin-right: 3px;
cursor: pointer;
}
.attendees-list div.attendee {
border-top: 1px solid #ccc;
}
.attendees-list span.attendee {
padding-left: 20px;
margin-right: 2em;
}
.attendees-list .organizer {
background-position: 3px -77px;
}
.attendees-list .opt-participant {
background-position: 2px -117px;
}
.attendees-list .chair {
background-position: 2px -137px;
}
.attendees-list .loading {
background: url(images/loading_blue.gif) 1px 50% no-repeat;
}
.attendees-list .total {
background: none;
padding-left: 4px;
font-weight: bold;
}
.attendees-list .spacer,
#schedule-freebusy-times tr.spacer td {
background: 0;
font-size: 50%;
}
#schedule-freebusy-times {
border-collapse: collapse;
width: 100%;
}
#schedule-freebusy-times td {
padding: 3px;
border: 1px solid #ccc;
}
#schedule-freebusy-times tr.dates th {
border-color: #aaa;
border-style: solid;
border-width: 0 1px 0 1px;
white-space: nowrap;
}
#attendees-freebusy-table div.timesheader,
#schedule-freebusy-times tr.times td {
min-width: 30px;
font-size: 9px;
padding: 5px 2px 6px 2px;
text-align: center;
}
#schedule-freebusy-times tr.times td.allday {
min-width: 60px;
}
#schedule-freebusy-times tr.times td {
cursor: pointer;
}
#schedule-event-time {
position: absolute;
border: 2px solid #333;
background: #777;
background: rgba(60, 60, 60, 0.6);
opacity: 0.5;
border-radius: 4px;
cursor: move;
filter: alpha(opacity=40); /* IE8 */
}
#eventfreebusy .schedule-options {
position: relative;
margin-bottom: 1.5em;
}
#eventfreebusy .schedule-buttons {
position: absolute;
top: 0;
right: 0;
}
#eventfreebusy .schedule-find-buttons {
padding-bottom:0.5em;
}
#eventfreebusy .schedule-find-buttons button {
min-width: 9em;
text-align: center;
}
span.edit-alarm-set {
white-space: nowrap;
}
a.dropdown-link {
color: #CC0000;
font-size: 12px;
text-decoration: none;
}
a.dropdown-link:after {
content: ' ▼';
font-size: 11px;
color: #666;
}
#eventedit .ui-tabs-panel {
min-height: 20em;
}
.alarm-item {
margin: 0.4em 0 1em 0;
}
.alarm-item .event-title {
font-size: 14px;
margin: 0.1em 0 0.3em 0;
}
.alarm-item div.event-section {
margin-top: 0.1em;
margin-bottom: 0.3em;
}
.alarm-item .alarm-actions {
margin-top: 0.4em;
}
.alarm-item div.alarm-actions a {
color: #CC0000;
margin-right: 0.8em;
text-decoration: none;
}
a.alarm-action-snooze:after {
content: ' ▼';
font-size: 10px;
color: #666;
}
#alarm-snooze-dropdown {
z-index: 5000;
}
.ui-dialog-buttonset a.dropdown-link {
margin-right: 1em;
}
/*
.ui-datepicker-calendar .ui-datepicker-today .ui-state-default {
border-color: #cccccc;
background: #ffffcc;
color: #000;
}
*/
.ui-datepicker-calendar .ui-datepicker-week-col {
border: 0;
color: #999;
font-size: 90%;
text-align: right;
padding-right: 6px;
}
/*
.ui-datepicker th {
padding: 0.3em 0;
font-size: 10px;
}
.ui-datepicker td span,
.ui-datepicker td a {
padding-left: 0.1em;
}
*/
.ui-autocomplete {
max-height: 160px;
overflow-y: auto;
overflow-x: hidden;
}
.ui-autocomplete .ui-menu-item {
white-space: nowrap;
}
* html .ui-autocomplete {
height: 160px;
}
span.spacer {
padding-left: 3em;
}
#agendaoptions {
position: absolute;
bottom: 28px;
left: 0;
right: 0;
height: auto;
z-index: 200;
background: #d6eaf3;
border: 1px solid #c3c3c3;
border-top-color: #ddd;
padding: 4px 5px;
}
#agendaoptions label {
color: #69939e;
text-shadow: 1px 1px #f2f2f2;
padding-right: 0.5em;
}
#calendar-kolabform {
position: relative;
margin: 0 -8px;
min-width: 660px;
min-height: 400px;
}
#calendar-kolabform table td.title {
font-weight: bold;
white-space: nowrap;
color: #666;
padding-right: 10px;
}
.propform fieldset.tab {
background: #efefef;
display: block;
margin-top: 0.5em;
padding: 0.5em 1em;
}
/* fullcalendar style overrides */
.rcube-fc-content {
overflow: hidden;
border: 0;
border-radius: 4px 4px 0 0;
box-shadow: 0 0 2px #999;
-o-box-shadow: 0 0 2px #999;
-webkit-box-shadow: 0 0 2px #999;
-moz-box-shadow: 0 0 2px #999;
}
.fc-content {
position: absolute !important;
top: 38px;
left: 0;
right: 0;
bottom: 28px;
background: #fff;
}
#fish-eye-view .fc-content {
top: 2px;
bottom: 2px;
}
.calendarmain .fc-button,
.calendarmain .fc-button.fc-state-hover,
.calendarmain .fc-button.fc-state-down {
border: 0;
background: none;
}
.calendarmain .fc-state-default .fc-button-inner,
.calendarmain .fc-state-hover .fc-button-inner {
margin: 0 0 0 0;
color: #555;
text-shadow: 0px 1px 1px #fff;
border: 1px solid #a2a2a2;
background: #f7f7f7;
background: -moz-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9f9f9), color-stop(100%,#e6e6e6));
background: -o-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: -ms-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-o-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-webkit-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-moz-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
text-decoration: none;
}
.calendarmain .fc-state-disabled .fc-button-inner {
color: #bbb;
}
.calendarmain .fc-header .fc-button {
margin-left: -1px;
margin-right: 0;
}
.calendarmain .fc-state-down .fc-button-inner {
margin: 0;
border: 1px solid #a2a2a2;
background: #e6e6e6;
background: -moz-linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e6e6e6), color-stop(100%,#f9f9f9));
background: -o-linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
background: -ms-linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
background: linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
}
.calendarmain .fc-state-active .fc-button-inner {
color: #333;
background: #bababa;
background: -moz-linear-gradient(top, #bababa 0%, #d8d8d8 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bababa), color-stop(100%,#d8d8d8));
background: -o-linear-gradient(top, #bababa 0%, #d8d8d8 100%);
background: -ms-linear-gradient(top, #bababa 0%, #d8d8d8 100%);
background: linear-gradient(top, #bababa 0%, #d8d8d8 100%);
}
.calendarmain .fc-corner-left .fc-button-inner,
.calendarmain .fc-corner-left .fc-button-content {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.calendarmain .fc-corner-right .fc-button-inner,
.calendarmain .fc-corner-right .fc-button-content {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.calendarmain .fc-state-default .fc-button-effect {
display: none;
}
.calendarmain .fc-button-content {
height: 2.2em;
line-height: 2.2em;
}
#calendar .fc-header-right {
padding-right: 250px;
}
.fc-event {
font-size: 1em !important;
}
.fc-event-title {
font-weight: bold;
}
.fc-event-hori .fc-event-title {
font-weight: normal;
white-space: nowrap;
}
.fc-event-hori .fc-event-time {
white-space: nowrap;
font-weight: normal !important;
font-size: 10px;
padding-right: 0.6em;
}
.fc-grid .fc-event-time {
font-weight: normal !important;
padding-right: 0.3em;
}
.fc-event-cateories {
font-style:italic;
}
div.fc-event-location {
font-size: 90%;
}
.fc-more-link {
color: #999;
padding-top: 1px;
cursor: pointer;
}
.fc-agenda-slots td div {
height: 22px;
}
.fc-sat, .fc-sun {
background-color: #fdfdfd;
}
.fc-widget-header {
background-color: #d6eaf3;
color: #004458;
text-shadow: 0px 1px 1px #fff;
}
.fc-view thead th.fc-widget-header {
padding: 8px 0;
color: #69939e;
}
.fc-day-number {
color: #578da5;
}
.fc-icon-alarms,
.fc-icon-sensitive,
.fc-icon-recurring {
display: inline-block;
width: 11px;
height: 11px;
background: url(images/eventicons.gif) 0 0 no-repeat;
margin-left: 3px;
line-height: 10px;
}
.fc-icon-alarms {
background-position: 0 -13px;
}
.fc-icon-sensitive {
background-position: 0 -25px;
}
.fc-list-section .fc-event {
cursor: pointer;
}
/*.calendarmain .fc-view-list div.fc-list-header,*/
.calendarmain .fc-view-table td.fc-list-header,
#edit-attendees-table thead td {
color: #69939e;
font-size: 11px;
font-weight: bold;
background: #d6eaf3;
background: -moz-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0,#e3f2f6), color-stop(8%,#d6eaf3), color-stop(100%,#d6eaf3));
background: -o-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
background: -ms-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px ,#d6eaf3 100%);
background: linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
border: 0;
padding: 7px;
}
.calendarmain .fc-view-table tr.fc-event td {
border-color: #ddd;
padding: 4px 7px;
}
.calendarmain .fc-view-table col.fc-event-location {
width: 20%;
}
.calendarmain .fc-view-table tr.fc-event td.fc-event-handle {
padding: 5px 10px 2px 7px;
width: 12px;
}
.calendarmain .fc-view-table .fc-event-handle .fc-event-skin {
margin: 0;
padding: 0;
display: inline-block;
width: 10px;
height: 10px;
font-size: 6px;
border-radius: 8px;
}
.calendarmain .fc-view-table .fc-event-handle .fc-event-inner {
display: inline-block;
width: 10px;
height: 10px;
padding: 0;
font-size: 10px;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.4);
-webkit-box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
-moz-box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
}
.fc-listappend {
text-align: center;
margin: 1em 0;
}
.fc-listappend .message {
padding: 0.5em;
margin-bottom: 0.5em;
font-size: 150%;
color: #999;
}
.fc-listappend .formlinks a {
font-size: 12px;
padding: 0 0.3em;
}
.fc-event-temp {
opacity: 0.4;
filter: alpha(opacity=40); /* IE8 */
}
/* Settings section */
fieldset #calendarcategories div {
margin-bottom: 0.3em;
}
/* Invitation UI in mail */
#messagemenu li a.calendarlink span.calendar {
-/* background-position: 7px -109px; */
+ background-position: 0px -1948px;
}
div.calendar-invitebox {
min-height: 20px;
margin: 5px 8px;
padding: 3px 6px 6px 34px;
border: 1px solid #ffdf0e;
background: url(images/calendar.png) 6px 5px no-repeat #fef893;
}
div.calendar-invitebox td.ititle {
font-weight: bold;
padding-right: 0.5em;
}
div.calendar-invitebox td.label {
color: #666;
padding-right: 1em;
}
#event-rsvp .rsvp-buttons,
div.calendar-invitebox .rsvp-status,
div.calendar-invitebox .rsvp-buttons {
margin-top: 0.5em;
}
#event-rsvp input.button,
div.calendar-invitebox input.button {
font-weight: bold;
margin-right: 0.5em;
}
div.calendar-invitebox .calendar-select {
font-weight: 10px;
margin-left: 1em;
}
div.calendar-invitebox .rsvp-status.loading {
color: #666;
padding: 1px 0 2px 24px;
background: url(images/loading_blue.gif) top left no-repeat;
}
div.calendar-invitebox .rsvp-status.declined,
div.calendar-invitebox .rsvp-status.tentative,
div.calendar-invitebox .rsvp-status.accepted {
padding: 0 0 1px 22px;
background: url(images/attendee-status.gif) 2px -20px no-repeat;
}
div.calendar-invitebox .rsvp-status.declined {
background-position: 2px -40px;
}
div.calendar-invitebox .rsvp-status.tentative {
background-position: 2px -60px;
}
/* iTIP attend reply page */
.calendaritipattend .centerbox {
width: 40em;
margin: 80px auto;
padding: 10px 10px 10px 90px;
background: url(images/invitation.png) 10px 10px no-repeat #fff;
}
.calendaritipattend .calendar-invitebox {
background: none;
padding-left: 0;
border: 0;
margin: 0 0 2em 0;
}
.calendaritipattend .calendar-invitebox .rsvp-status {
margin-top: 2.5em;
font-size: 110%;
font-weight: bold;
}
.calendaritipattend .calendar-invitebox td.title,
.calendaritipattend .calendar-invitebox td.ititle {
font-size: 120%;
}
diff --git a/plugins/kolab_folders/kolab_folders.php b/plugins/kolab_folders/kolab_folders.php
index ab84c24c..e1b2e634 100644
--- a/plugins/kolab_folders/kolab_folders.php
+++ b/plugins/kolab_folders/kolab_folders.php
@@ -1,661 +1,668 @@
<?php
/**
* Type-aware folder management/listing for Kolab
*
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
*
* 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/>.
*/
class kolab_folders extends rcube_plugin
{
public $task = '?(?!login).*';
public $types = array('mail', 'event', 'journal', 'task', 'note', 'contact', 'configuration');
public $mail_types = array('inbox', 'drafts', 'sentitems', 'outbox', 'wastebasket', 'junkemail');
private $rc;
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
/**
* Plugin initialization.
*/
function init()
{
$this->rc = rcmail::get_instance();
// Folder listing hooks
$this->add_hook('storage_folders', array($this, 'mailboxes_list'));
// Folder manager hooks
$this->add_hook('folder_form', array($this, 'folder_form'));
$this->add_hook('folder_update', array($this, 'folder_save'));
$this->add_hook('folder_create', array($this, 'folder_save'));
$this->add_hook('folder_delete', array($this, 'folder_save'));
$this->add_hook('folder_rename', array($this, 'folder_save'));
$this->add_hook('folders_list', array($this, 'folders_list'));
}
/**
* Handler for mailboxes_list hook. Enables type-aware lists filtering.
*/
function mailboxes_list($args)
{
if (!$this->metadata_support()) {
return $args;
}
$filter = $args['filter'];
// all-folders request, use core method
if (!$filter) {
return $args;
}
// get folders types
$folderdata = $this->get_folder_type_list($args['root'].$args['name'], true);
if (!is_array($folderdata)) {
return $args;
}
$regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
// In some conditions we can skip LIST command (?)
if ($args['mode'] == 'LIST' && $filter != 'mail'
&& $args['root'] == '' && $args['name'] == '*'
) {
foreach ($folderdata as $folder => $type) {
if (!preg_match($regexp, $type)) {
unset($folderdata[$folder]);
}
}
$args['folders'] = array_keys($folderdata);
return $args;
}
$storage = $this->rc->get_storage();
// Get folders list
if ($args['mode'] == 'LIST') {
+ if (!$storage->check_connection()) {
+ return $args;
+ }
$args['folders'] = $storage->conn->listMailboxes($args['root'], $args['name']);
}
else {
$args['folders'] = $this->list_subscribed($args['root'], $args['name']);
}
// In case of an error, return empty list
if (!is_array($args['folders'])) {
$args['folders'] = array();
return $args;
}
// Filter folders list
foreach ($args['folders'] as $idx => $folder) {
$type = $folderdata[$folder];
if ($filter == 'mail' && empty($type)) {
continue;
}
if (empty($type) || !preg_match($regexp, $type)) {
unset($args['folders'][$idx]);
}
}
return $args;
}
/**
* Handler for folders_list hook. Add css classes to folder rows.
*/
function folders_list($args)
{
if (!$this->metadata_support()) {
return $args;
}
$table = $args['table'];
// get folders types
$folderdata = $this->get_folder_type_list('*');
if (!is_array($folderdata)) {
return $args;
}
// Add type-based style for table rows
// See kolab_folders::folder_class_name()
for ($i=1, $cnt=$table->size(); $i<=$cnt; $i++) {
$attrib = $table->get_row_attribs($i);
$folder = $attrib['foldername']; // UTF7-IMAP
$type = $folderdata[$folder];
if (!$type)
$type = 'mail';
$class_name = self::folder_class_name($type);
$attrib['class'] = trim($attrib['class'] . ' ' . $class_name);
$table->set_row_attribs($attrib, $i);
}
return $args;
}
/**
* Handler for folder info/edit form (folder_form hook).
* Adds folder type selector.
*/
function folder_form($args)
{
if (!$this->metadata_support()) {
return $args;
}
// load translations
$this->add_texts('localization/', false);
// INBOX folder is of type mail.inbox and this cannot be changed
if ($args['name'] == 'INBOX') {
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => sprintf('%s (%s)', $this->gettext('foldertypemail'), $this->gettext('inbox')),
);
return $args;
}
if ($args['options']['is_root']) {
return $args;
}
$mbox = strlen($args['name']) ? $args['name'] : $args['parent_name'];
if (isset($_POST['_ctype'])) {
$new_ctype = trim(get_input_value('_ctype', RCUBE_INPUT_POST));
$new_subtype = trim(get_input_value('_subtype', RCUBE_INPUT_POST));
}
// Get type of the folder or the parent
if (strlen($mbox)) {
list($ctype, $subtype) = $this->get_folder_type($mbox);
if (strlen($args['parent_name']) && $subtype == 'default')
$subtype = ''; // there can be only one
}
if (!$ctype) {
$ctype = 'mail';
}
$storage = $this->rc->get_storage();
// Don't allow changing type of shared folder, according to ACL
if (strlen($mbox)) {
$options = $storage->folder_info($mbox);
if ($options['namespace'] != 'personal' && !in_array('a', $options['rights'])) {
if (in_array($ctype, $this->types)) {
$value = $this->gettext('foldertype'.$ctype);
}
else {
$value = $ctype;
}
if ($subtype) {
$value .= ' ('. ($subtype == 'default' ? $this->gettext('default') : $subtype) .')';
}
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => $value,
);
return $args;
}
}
// Add javascript script to the client
$this->include_script('kolab_folders.js');
// build type SELECT fields
$type_select = new html_select(array('name' => '_ctype', 'id' => '_ctype'));
$sub_select = new html_select(array('name' => '_subtype', 'id' => '_subtype'));
foreach ($this->types as $type) {
$type_select->add($this->gettext('foldertype'.$type), $type);
}
// add non-supported type
if (!in_array($ctype, $this->types)) {
$type_select->add($ctype, $ctype);
}
$sub_select->add('', '');
$sub_select->add($this->gettext('default'), 'default');
foreach ($this->mail_types as $type) {
$sub_select->add($this->gettext($type), $type);
}
$args['form']['props']['fieldsets']['settings']['content']['foldertype'] = array(
'label' => $this->gettext('folderctype'),
'value' => $type_select->show(isset($new_ctype) ? $new_ctype : $ctype)
. $sub_select->show(isset($new_subtype) ? $new_subtype : $subtype),
);
return $args;
}
/**
* Handler for folder update/create action (folder_update/folder_create hook).
*/
function folder_save($args)
{
// Folder actions from folders list
if (empty($args['record'])) {
// Just clear Horde folders cache and return
$this->clear_folders_cache();
return $args;
}
// Folder create/update with form
$ctype = trim(get_input_value('_ctype', RCUBE_INPUT_POST));
$subtype = trim(get_input_value('_subtype', RCUBE_INPUT_POST));
$mbox = $args['record']['name'];
$old_mbox = $args['record']['oldname'];
$subscribe = $args['record']['subscribe'];
if (empty($ctype)) {
return $args;
}
// load translations
$this->add_texts('localization/', false);
// Skip folder creation/rename in core
// @TODO: Maybe we should provide folder_create_after and folder_update_after hooks?
// Using create_mailbox/rename_mailbox here looks bad
$args['abort'] = true;
// There can be only one default folder of specified type
if ($subtype == 'default') {
$default = $this->get_default_folder($ctype);
if ($default !== null && $old_mbox != $default) {
$args['result'] = false;
$args['message'] = $this->gettext('defaultfolderexists');
return $args;
}
}
// Subtype sanity-checks
else if ($subtype && ($ctype != 'mail' || !in_array($subtype, $this->mail_types))) {
$subtype = '';
}
$ctype .= $subtype ? '.'.$subtype : '';
$storage = $this->rc->get_storage();
// Create folder
if (!strlen($old_mbox)) {
// By default don't subscribe to non-mail folders
if ($subscribe)
$subscribe = (bool) preg_match('/^mail/', $ctype);
$result = $storage->create_folder($mbox, $subscribe);
// Set folder type
if ($result) {
$this->set_folder_type($mbox, $ctype);
}
}
// Rename folder
else {
if ($old_mbox != $mbox) {
$result = $storage->rename_folder($old_mbox, $mbox);
}
else {
$result = true;
}
if ($result) {
list($oldtype, $oldsubtype) = $this->get_folder_type($mbox);
$oldtype .= $oldsubtype ? '.'.$oldsubtype : '';
if ($ctype != $oldtype) {
$this->set_folder_type($mbox, $ctype);
}
}
}
// Clear Horde folders cache
if ($result) {
$this->clear_folders_cache();
}
$args['record']['class'] = self::folder_class_name($ctype);
$args['record']['subscribe'] = $subscribe;
$args['result'] = $result;
return $args;
}
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @return boolean
*/
function metadata_support()
{
$storage = $this->rc->get_storage();
return $storage->get_capability('METADATA') ||
$storage->get_capability('ANNOTATEMORE') ||
$storage->get_capability('ANNOTATEMORE2');
}
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @param string $folder Folder name
*
* @return array Folder content-type
*/
function get_folder_type($folder)
{
$storage = $this->rc->get_storage();
$folderdata = $storage->get_metadata($folder, array(kolab_folders::CTYPE_KEY));
return explode('.', $folderdata[$folder][kolab_folders::CTYPE_KEY]);
}
/**
* Sets folder content-type.
*
* @param string $folder Folder name
* @param string $type Content type
*
* @return boolean True on success
*/
function set_folder_type($folder, $type='mail')
{
$storage = $this->rc->get_storage();
return $storage->set_metadata($folder, array(kolab_folders::CTYPE_KEY => $type));
}
/**
* Returns list of subscribed folders (directly from IMAP server)
*
* @param string $root Optional root folder
* @param string $name Optional name pattern
*
* @return array List of mailboxes/folders
*/
private function list_subscribed($root='', $name='*')
{
$storage = $this->rc->get_storage();
+ if (!$storage->check_connection()) {
+ return null;
+ }
+
// Code copied from rcube_imap::_list_mailboxes()
// Server supports LIST-EXTENDED, we can use selection options
// #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
- if (!$this->rc->config->get('imap_force_lsub') && $storage->get_capability('LIST-EXTENDED')) {
+ if (!$this->rc->config->get('imap_force_lsub') && $imap->get_capability('LIST-EXTENDED')) {
// This will also set mailbox options, LSUB doesn't do that
$a_folders = $storage->conn->listMailboxes($root, $name,
NULL, array('SUBSCRIBED'));
// remove non-existent folders
- if (is_array($a_folders) && $name = '*') {
+ if (is_array($a_folders) && $name = '*' && !empty($storage->conn->data['LIST'])) {
foreach ($a_folders as $idx => $folder) {
- if ($storage->conn->data['LIST'] && ($opts = $storage->conn->data['LIST'][$folder])
+ if (($opts = $storage->conn->data['LIST'][$folder])
&& in_array('\\NonExistent', $opts)
) {
$storage->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
// retrieve list of folders from IMAP server using LSUB
else {
$a_folders = $storage->conn->listSubscribed($root, $name);
// unsubscribe non-existent folders, remove from the list
- if (is_array($a_folders) && $name == '*') {
+ if (is_array($a_folders) && $name == '*' && !empty($storage->conn->data['LIST'])) {
foreach ($a_folders as $idx => $folder) {
- if ($storage->conn->data['LIST'] && ($opts = $storage->conn->data['LIST'][$folder])
- && in_array('\\Noselect', $opts)
+ if (!isset($storage->conn->data['LIST'][$folder])
+ || in_array('\\Noselect', $storage->conn->data['LIST'][$folder])
) {
// Some servers returns \Noselect for existing folders
if (!$storage->folder_exists($folder)) {
$storage->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
}
return $a_folders;
}
/**
* Returns list of folder(s) type(s)
*
* @param string $mbox Folder name or pattern
* @param bool $defaults Enables creation of configured default folders
*
* @return array List of folders data, indexed by folder name
*/
function get_folder_type_list($mbox, $create_defaults = false)
{
$storage = $this->rc->get_storage();
// Use mailboxes. prefix so the cache will be cleared by core
// together with other mailboxes-related cache data
$cache_key = 'mailboxes.folder-type.'.$mbox;
// get cached metadata
$metadata = $storage->get_cache($cache_key);
if (!is_array($metadata)) {
$metadata = $storage->get_metadata($mbox, kolab_folders::CTYPE_KEY);
$need_update = true;
}
if (!is_array($metadata)) {
return false;
}
// make the result more flat
if ($need_update) {
$metadata = array_map('implode', $metadata);
}
// create default folders if needed
if ($create_defaults) {
$this->create_default_folders($metadata, $cache_key);
}
// write mailboxlist to cache
if ($need_update) {
$storage->update_cache($cache_key, $metadata);
}
return $metadata;
}
/**
* Returns the name of default folder
*
* @param string $type Folder type
*
* @return string Folder name
*/
function get_default_folder($type)
{
$storage = $this->rc->get_storage();
$folderdata = $this->get_folder_type_list('*');
if (!is_array($folderdata)) {
return null;
}
$type .= '.default';
$namespace = $storage->get_namespace();
// get all folders of specified type
$folderdata = array_intersect($folderdata, array($type));
unset($folders[0]);
foreach ($folderdata as $folder => $data) {
// check if folder is in personal namespace
foreach (array('shared', 'other') as $nskey) {
if (!empty($namespace[$nskey])) {
foreach ($namespace[$nskey] as $ns) {
if ($ns[0] && substr($folder, 0, strlen($ns[0])) == $ns[0]) {
continue 3;
}
}
}
}
// There can be only one default folder of specified type
return $folder;
}
return null;
}
/**
* Returns CSS class name for specified folder type
*
* @param string $type Folder type
*
* @return string Class name
*/
static function folder_class_name($type)
{
list($ctype, $subtype) = explode('.', $type);
$class[] = 'type-' . ($ctype ? $ctype : 'mail');
if ($subtype)
$class[] = 'subtype-' . $subtype;
return implode(' ', $class);
}
/**
* Clear Horde's folder cache. See Kolab_List::singleton().
*/
private function clear_folders_cache()
{
unset($_SESSION['horde_session_objects']['kolab_folderlist']);
}
/**
* Creates default folders if they doesn't exist
*/
private function create_default_folders(&$folderdata, $cache_key = null)
{
$storage = $this->rc->get_storage();
$namespace = $storage->get_namespace();
$defaults = array();
$need_update = false;
// Find personal namespace prefix
if (is_array($namespace['personal']) && count($namespace['personal']) == 1) {
$prefix = $namespace['personal'][0][0];
}
else {
$prefix = '';
}
$this->load_config();
// get configured defaults
foreach ($this->types as $type) {
$subtypes = $type == 'mail' ? $this->mail_types : array('default');
foreach ($subtypes as $subtype) {
$opt_name = 'kolab_folders_' . $type . '_' . $subtype;
if ($folder = $this->rc->config->get($opt_name)) {
// convert configuration value to UTF7-IMAP charset
$folder = rcube_charset::convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
// and namespace prefix if needed
if ($prefix && strpos($folder, $prefix) === false && $folder != 'INBOX') {
$folder = $prefix . $folder;
}
$defaults[$type . '.' . $subtype] = $folder;
}
}
}
// find default folders
foreach ($defaults as $type => $foldername) {
// folder exists, do nothing
if (!empty($folderdata[$foldername])) {
continue;
}
// special case, need to set type only
if ($foldername == 'INBOX' || $type == 'mail.inbox') {
$this->set_folder_type($foldername, 'mail.inbox');
continue;
}
// get all folders of specified type
$folders = array_intersect($folderdata, array($type));
unset($folders[0]);
// find folders in personal namespace
foreach ($folders as $folder) {
if ($folder) {
foreach (array('shared', 'other') as $nskey) {
if (!empty($namespace[$nskey])) {
foreach ($namespace[$nskey] as $ns) {
if ($ns[0] && substr($folder, 0, strlen($ns[0])) == $ns[0]) {
continue 3;
}
}
}
}
}
// got folder in personal namespace
continue 2;
}
list($type1, $type2) = explode('.', $type);
// create folder
if ($type1 != 'mail' || !$storage->folder_exists($foldername)) {
$storage->create_folder($foldername, $type1 == 'mail');
}
// set type
$result = $this->set_folder_type($foldername, $type);
// add new folder to the result
if ($result) {
$folderdata[$foldername] = $type;
$need_update = true;
}
}
// update cache
if ($need_update && $cache_key) {
$storage->update_cache($cache_key, $folderdata);
}
}
}
diff --git a/plugins/kolab_zpush/kolab_zpush.php b/plugins/kolab_zpush/kolab_zpush.php
index f19fe3ad..4ef242cf 100644
--- a/plugins/kolab_zpush/kolab_zpush.php
+++ b/plugins/kolab_zpush/kolab_zpush.php
@@ -1,346 +1,346 @@
<?php
/**
* Z-Push configuration utility for Kolab accounts
*
* @version 0.2
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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/>.
*/
class kolab_zpush extends rcube_plugin
{
public $task = 'settings';
public $urlbase;
private $rc;
private $ui;
private $cache;
private $devices;
private $folders;
private $folders_meta;
private $root_meta;
const ROOT_MAILBOX = 'INBOX';
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
const ACTIVESYNC_KEY = '/private/vendor/kolab/activesync';
/**
* Plugin initialization.
*/
public function init()
{
$this->rc = rcmail::get_instance();
$this->require_plugin('jqueryui');
$this->add_texts('localization/', true);
$this->include_script('kolab_zpush.js');
$this->register_action('plugin.zpushconfig', array($this, 'config_view'));
$this->register_action('plugin.zpushjson', array($this, 'json_command'));
if ($this->rc->action == 'plugin.zpushconfig')
$this->require_plugin('kolab_core');
}
/**
* Establish IMAP connection
*/
public function init_imap()
{
$storage = $this->rc->get_storage();
$this->cache = $this->rc->get_cache('zpush', 'db', 900);
$this->cache->expunge();
if ($meta = $storage->get_metadata(self::ROOT_MAILBOX, self::ACTIVESYNC_KEY)) {
// clear cache if device config changed
if (($oldmeta = $this->cache->read('devicemeta')) && $oldmeta != $meta)
$this->cache->remove();
$this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ACTIVESYNC_KEY]);
$this->cache->remove('devicemeta');
$this->cache->write('devicemeta', $meta);
}
}
/**
* Handle JSON requests
*/
public function json_command()
{
$storage = $this->rc->get_storage();
$cmd = get_input_value('cmd', RCUBE_INPUT_GPC);
$imei = get_input_value('id', RCUBE_INPUT_GPC);
switch ($cmd) {
case 'load':
$result = array();
$devices = $this->list_devices();
if ($device = $devices[$imei]) {
$result['id'] = $imei;
$result['devicealias'] = $device['ALIAS'];
$result['syncmode'] = intval($device['MODE']);
$result['laxpic'] = intval($device['LAXPIC']);
$result['subscribed'] = array();
foreach ($this->folders_meta() as $folder => $meta) {
if ($meta[$imei]['S'])
$result['subscribed'][$folder] = intval($meta[$imei]['S']);
}
$this->rc->output->command('plugin.zpush_data_ready', $result);
}
else {
$this->rc->output->show_message($this->gettext('devicenotfound'), 'error');
}
break;
case 'save':
$devices = $this->list_devices();
$syncmode = intval(get_input_value('syncmode', RCUBE_INPUT_POST));
$devicealias = get_input_value('devicealias', RCUBE_INPUT_POST, true);
$laxpic = intval(get_input_value('laxpic', RCUBE_INPUT_POST));
$subsciptions = get_input_value('subscribed', RCUBE_INPUT_POST);
$err = false;
if ($device = $devices[$imei]) {
// update device config if changed
if ($devicealias != $this->root_meta['DEVICE'][$imei]['ALIAS'] ||
$syncmode != $this->root_meta['DEVICE'][$imei]['MODE'] ||
$laxpic != $this->root_meta['DEVICE'][$imei]['LAXPIC'] ||
$subsciptions[self::ROOT_MAILBOX] != $this->root_meta['FOLDER'][$imei]['S']) {
$this->root_meta['DEVICE'][$imei]['MODE'] = $syncmode;
$this->root_meta['DEVICE'][$imei]['ALIAS'] = $devicealias;
$this->root_meta['DEVICE'][$imei]['LAXPIC'] = $laxpic;
$this->root_meta['FOLDER'][$imei]['S'] = intval($subsciptions[self::ROOT_MAILBOX]);
$err = !$storage->set_metadata(self::ROOT_MAILBOX,
array(self::ACTIVESYNC_KEY => $this->serialize_metadata($this->root_meta)));
// update cached meta data
if (!$err) {
$this->cache->remove('devicemeta');
$this->cache->write('devicemeta', $storage->get_metadata(self::ROOT_MAILBOX, self::ACTIVESYNC_KEY));
}
}
// iterate over folders list and update metadata if necessary
foreach ($this->folders_meta() as $folder => $meta) {
// skip root folder (already handled above)
if ($folder == self::ROOT_MAILBOX)
continue;
if ($subsciptions[$folder] != $meta[$imei]['S']) {
$meta[$imei]['S'] = intval($subsciptions[$folder]);
$this->folders_meta[$folder] = $meta;
unset($meta['TYPE']);
// read metadata first
$folderdata = $storage->get_metadata($folder, array(self::ACTIVESYNC_KEY));
if ($asyncdata = $folderdata[$folder][self::ACTIVESYNC_KEY])
$metadata = $this->unserialize_metadata($asyncdata);
$metadata['FOLDER'] = $meta;
$err |= !$storage->set_metadata($folder, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($metadata)));
}
}
// update cache
$this->cache->remove('folders');
$this->cache->write('folders', $this->folders_meta);
$this->rc->output->command('plugin.zpush_save_complete', array('success' => !$err, 'id' => $imei, 'devicealias' => Q($devicealias)));
}
if ($err)
$this->rc->output->show_message($this->gettext('savingerror'), 'error');
else
$this->rc->output->show_message($this->gettext('successfullysaved'), 'confirmation');
break;
case 'delete':
- $this->init_imap();
$devices = $this->list_devices();
if ($device = $devices[$imei]) {
unset($this->root_meta['DEVICE'][$imei], $this->root_meta['FOLDER'][$imei]);
// update annotation and cached meta data
if ($success = $storage->set_metadata(self::ROOT_MAILBOX, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($this->root_meta)))) {
$this->cache->remove('devicemeta');
$this->cache->write('devicemeta', $storage->get_metadata(self::ROOT_MAILBOX, self::ACTIVESYNC_KEY));
// remove device annotation in every folder
foreach ($this->folders_meta() as $folder => $meta) {
// skip root folder (already handled above)
if ($folder == self::ROOT_MAILBOX)
continue;
if (isset($meta[$imei])) {
$type = $meta['TYPE']; // remember folder type
unset($meta[$imei], $meta['TYPE']);
// read metadata first and update FOLDER property
$folderdata = $storage->get_metadata($folder, array(self::ACTIVESYNC_KEY));
if ($asyncdata = $folderdata[$folder][self::ACTIVESYNC_KEY])
$metadata = $this->unserialize_metadata($asyncdata);
$metadata['FOLDER'] = $meta;
if ($storage->set_metadata($folder, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($metadata)))) {
$this->folders_meta[$folder] = $metadata;
$this->folders_meta[$folder]['TYPE'] = $type;
}
}
}
// update cache
$this->cache->remove('folders');
$this->cache->write('folders', $this->folders_meta);
}
}
if ($success) {
$this->rc->output->show_message($this->gettext('successfullydeleted'), 'confirmation');
$this->rc->output->redirect(array('action' => 'plugin.zpushconfig')); // reload UI
}
else
$this->rc->output->show_message($this->gettext('savingerror'), 'error');
break;
}
$this->rc->output->send();
}
/**
* Render main UI for device configuration
*/
public function config_view()
{
require_once $this->home . '/kolab_zpush_ui.php';
$storage = $this->rc->get_storage();
// checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
if (!($storage->get_capability('METADATA') || $storage->get_capability('ANNOTATEMORE') || $storage->get_capability('ANNOTATEMORE2'))) {
$this->rc->output->show_message($this->gettext('notsupported'), 'error');
}
$this->ui = new kolab_zpush_ui($this);
$this->register_handler('plugin.devicelist', array($this->ui, 'device_list'));
$this->register_handler('plugin.deviceconfigform', array($this->ui, 'device_config_form'));
$this->register_handler('plugin.foldersubscriptions', array($this->ui, 'folder_subscriptions'));
$this->rc->output->set_env('devicecount', count($this->list_devices()));
$this->rc->output->send('kolab_zpush.config');
}
/**
* List known devices
*
* @return array Device list as hash array
*/
public function list_devices()
{
if (!isset($this->devices)) {
+ $this->init_imap();
$this->devices = (array)$this->root_meta['DEVICE'];
}
return $this->devices;
}
/**
* Get list of all folders available for sync
*
* @return array List of mailbox folders
*/
public function list_folders()
{
if (!isset($this->folders)) {
// read cached folder meta data
if ($cached_folders = $this->cache->read('folders')) {
$this->folders_meta = $cached_folders;
$this->folders = array_keys($this->folders_meta);
}
// fetch folder data from server
else {
$storage = $this->rc->get_storage();
$this->folders = $storage->list_folders();
foreach ($this->folders as $folder) {
$folderdata = $storage->get_metadata($folder, array(self::ACTIVESYNC_KEY, self::CTYPE_KEY));
$foldertype = explode('.', $folderdata[$folder][self::CTYPE_KEY]);
if ($asyncdata = $folderdata[$folder][self::ACTIVESYNC_KEY]) {
$metadata = $this->unserialize_metadata($asyncdata);
$this->folders_meta[$folder] = $metadata['FOLDER'];
}
$this->folders_meta[$folder]['TYPE'] = !empty($foldertype[0]) ? $foldertype[0] : 'mail';
}
// cache it!
$this->cache->write('folders', $this->folders_meta);
}
}
return $this->folders;
}
/**
* Getter for folder metadata
*
* @return array Hash array with meta data for each folder
*/
public function folders_meta()
{
if (!isset($this->folders_meta))
$this->list_folders();
return $this->folders_meta;
}
/**
* Helper method to decode saved IMAP metadata
*/
private function unserialize_metadata($str)
{
if (!empty($str))
return @json_decode(base64_decode($str), true);
return null;
}
/**
* Helper method to encode IMAP metadata for saving
*/
private function serialize_metadata($data)
{
if (is_array($data))
return base64_encode(json_encode($data));
return '';
}
}
diff --git a/plugins/kolab_zpush/localization/en_US.inc b/plugins/kolab_zpush/localization/en_US.inc
index 5160d91c..2537d4d5 100644
--- a/plugins/kolab_zpush/localization/en_US.inc
+++ b/plugins/kolab_zpush/localization/en_US.inc
@@ -1,32 +1,33 @@
<?php
$labels = array();
$labels['tabtitle'] = 'Activesync';
$labels['devices'] = 'Devices';
$labels['devicealias'] = 'Device name';
$labels['syncmode'] = 'Sync Mode';
$labels['modeauto'] = 'Determine automatically';
$labels['modeflat'] = 'Force flat mode';
$labels['modefolder'] = 'Force folder mode';
$labels['synchronize'] = 'Synchronize';
$labels['withalarms'] = 'With alarms';
$labels['syncsettings'] = 'Synchronization settings';
$labels['deviceconfig'] = 'Device configration';
$labels['folderstosync'] = 'Folders to synchronize';
$labels['mail'] = 'Email';
$labels['contact'] = 'Address Books';
$labels['event'] = 'Calendars';
$labels['task'] = 'Tasks';
$labels['note'] = 'Notes';
+$labels['configuration'] = 'Configuration';
$labels['deletedevice'] = 'Delete device';
$labels['imageformat'] = 'Image format';
$labels['laxpiclabel'] = 'Allow PNG and GIF images';
$labels['introtext'] = 'Select a device to configure';
$labels['nodevices'] = 'There are currently no devices registered.<br/><br/>In order to register a device, please connect it to the server first, using <a href="http://wiki.kolab.org/Z_push#Clients">the instructions in the Wiki</a>. Afterwards the device should become available for configuration here.';
$labels['savingdata'] = 'Saving data...';
$labels['savingerror'] = 'Failed to save configuration';
$labels['notsupported'] = 'Your server does not support metadata/annotations';
$labels['devicedeleteconfirm'] = 'Do you really want to delete the configuration for this device?';
$labels['successfullydeleted'] = 'The device configuration was successfully removed';
?>
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 571afc6a..050ff32d 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -1,734 +1,753 @@
<?php
/**
* The kolab_storage_folder class represents an IMAP folder on the Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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/>.
*/
class kolab_storage_folder
{
const KTYPE_PREFIX = 'application/x-vnd.kolab.';
/**
* The folder name.
*
* @var string
*/
public $name;
/**
* The type of this folder.
*
* @var string
*/
public $type;
private $type_annotation;
private $subpath;
private $imap;
private $info;
private $owner;
private $objcache = array();
private $uid2msg = array();
/**
* Default constructor
*/
function __construct($name, $imap = null)
{
$this->imap = is_object($imap) ? $imap : rcmail::get_instance()->get_storage();
$this->imap->set_options(array('skip_deleted' => false));
$this->set_folder($name);
}
/**
* Set the IMAP folder name this instance connects to
*
* @param string The folder name/path
*/
public function set_folder($name)
{
$this->name = $name;
$this->imap->set_folder($this->name);
$metadata = $this->imap->get_metadata($this->name, array(kolab_storage::CTYPE_KEY));
$this->type_annotation = $metadata[$this->name][kolab_storage::CTYPE_KEY];
$this->type = reset(explode('.', $this->type_annotation));
}
/**
*
*/
private function get_folder_info()
{
if (!isset($this->info))
$this->info = $this->imap->folder_info($this->name);
return $this->info;
}
/**
* Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
*
* @param array List of metadata keys to read
* @return array Metadata entry-value hash array on success, NULL on error
*/
public function get_metadata($keys)
{
$metadata = $this->imap->get_metadata($this->name, (array)$keys);
return $metadata[$this->name];
}
/**
* Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
*
* @param array $entries Entry-value array (use NULL value as NIL)
* @return boolean True on success, False on failure
*/
public function set_metadata($entries)
{
return $this->imap->set_metadata($this->name, $entries);
}
/**
* Returns the owner of the folder.
*
* @return string The owner of this folder.
*/
public function get_owner()
{
// return cached value
if (isset($this->owner))
return $this->owner;
$info = $this->get_folder_info();
$rcmail = rcmail::get_instance();
switch ($info['namespace']) {
case 'personal':
$this->owner = $rcmail->user->get_username();
break;
case 'shared':
$this->owner = 'anonymous';
break;
default:
$owner = '';
list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
if (strpos($user, '@') === false) {
$domain = strstr($rcmail->user->get_username(), '@');
if (!empty($domain))
$user .= $domain;
}
$this->owner = $user;
break;
}
return $this->owner;
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
return $this->imap->folder_namespace($this->name);
}
/**
* Get IMAP ACL information for this folder
*
* @return string Permissions as string
*/
public function get_myrights()
{
$rights = $this->info['rights'];
if (!is_array($rights))
$rights = $this->imap->my_rights($this->name);
return join('', (array)$rights);
}
/**
* Check subscription status of this folder
*
* @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION)
* @return boolean True if subscribed, false if not
*/
public function is_subscribed($type = 0)
{
static $subscribed; // local cache
if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) {
if (!$subscribed)
$subscribed = $this->imap->list_folders_subscribed();
return in_array($this->name, $subscribed);
}
else if (kolab_storage::CLIENTSIDE_SUBSCRIPTION) {
// TODO: implement this
return true;
}
return false;
}
+ /**
+ * Change subscription status of this folder
+ *
+ * @param boolean The desired subscription status: true = subscribed, false = not subscribed
+ * @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION)
+ * @return True on success, false on error
+ */
+ public function subscribe($subscribed, $type = 0)
+ {
+ if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) {
+ return $subscribed ? $this->imap->subscribe($this->name) : $this->imap->unsubscribe($this->name);
+ }
+ else {
+ // TODO: implement this
+ }
+
+ return false;
+ }
+
/**
* Get number of objects stored in this folder
*
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return integer The number of objects of the given type
*/
public function count($type = null)
{
if (!$type) $type = $this->type;
// search by object type
$ctype = self::KTYPE_PREFIX . $type;
$index = $this->imap->search_once($this->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype);
return $index->count();
}
/**
* List all Kolab objects of the given type
*
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return array List of Kolab data objects (each represented as hash array)
*/
public function get_objects($type = null)
{
if (!$type) $type = $this->type;
$ctype = self::KTYPE_PREFIX . $type;
$results = array();
// use 'list' for folder's default objects
if ($type == $this->type) {
$index = $this->imap->index($this->name);
}
else { // search by object type
$search = 'UNDELETED HEADER X-Kolab-Type ' . $ctype;
$index = $this->imap->search_once($this->name, $search);
}
// fetch all messages from IMAP
foreach ($index->get() as $msguid) {
if ($object = $this->read_object($msguid, $type)) {
$results[] = $object;
$this->uid2msg[$object['uid']] = $msguid;
}
}
// TODO: write $this->uid2msg to cache
return $results;
}
/**
* Getter for a single Kolab object, identified by its UID
*
* @param string Object UID
* @return array The Kolab object represented as hash array
*/
public function get_object($uid)
{
$msguid = $this->uid2msguid($uid);
if ($msguid && ($object = $this->read_object($msguid)))
return $object;
return false;
}
/**
* Fetch a Kolab object attachment which is stored in a separate part
* of the mail MIME message that represents the Kolab record.
*
* @param string Object's UID
* @param string The attachment's mime number
* @param string IMAP folder where message is stored;
* If set, that also implies that the given UID is an IMAP UID
* @return mixed The attachment content as binary string
*/
public function get_attachment($uid, $part, $mailbox = null)
{
if ($msguid = ($mailbox ? $uid : $this->uid2msguid($uid))) {
if ($mailbox)
$this->imap->set_folder($mailbox);
return $this->imap->get_message_part($msguid, $part);
}
return null;
}
/**
* Fetch the mime message from the storage server and extract
* the Kolab groupware object from it
*
* @param string The IMAP message UID to fetch
* @param string The object type expected
* @param string The folder name where the message is stored
* @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
*/
private function read_object($msguid, $type = null, $folder = null)
{
if (!$type) $type = $this->type;
if (!$folder) $folder = $this->name;
$ctype = self::KTYPE_PREFIX . $type;
// requested message in local cache
if ($this->objcache[$msguid])
return $this->objcache[$msguid];
$this->imap->set_folder($folder);
// check ctype header and abort on mismatch
$headers = $this->imap->get_message_headers($msguid);
if ($headers->others['x-kolab-type'] != $ctype)
return false;
$message = new rcube_message($msguid);
$attachments = array();
// get XML part
foreach ((array)$message->attachments as $part) {
if (!$xml && ($part->mimetype == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
}
else if ($part->filename) {
$attachments[$part->filename] = array(
'key' => $part->mime_id,
'type' => $part->mimetype,
'size' => $part->size,
);
}
}
if (!$xml) {
raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Could not find Kolab data part in message $msguid ($this->name).",
), true);
return false;
}
$format = kolab_format::factory($type);
// check kolab format version
if (strpos($xml, '<' . $type) !== false) {
// old Kolab 2.0 format detected
$handler = Horde_Kolab_Format::factory('XML', $type);
if (is_object($handler) && is_a($handler, 'PEAR_Error')) {
continue;
}
// XML-to-array
$object = $handler->load($xml);
$format->fromkolab2($object);
}
else {
// load Kolab 3 format using libkolabxml
$format->load($xml);
}
if ($format->is_valid()) {
if ($formatobj)
return $format;
$object = $format->to_array();
$object['_msguid'] = $msguid;
$object['_mailbox'] = $this->name;
$object['_attachments'] = $attachments;
$object['_formatobj'] = $format;
$this->objcache[$msguid] = $object;
return $object;
}
return false;
}
/**
* Save an object in this folder.
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param string $uid The UID of the old object if it existed before
* @return boolean True on success, false on error
*/
public function save(&$object, $type = null, $uid = null)
{
if (!$type)
$type = $this->type;
// copy attachments from old message
if (!empty($object['_msguid']) && ($old = $this->read_object($object['_msguid'], $type, $object['_mailbox']))) {
foreach ($old['_attachments'] as $name => $att) {
if (!isset($object['_attachments'][$name])) {
$object['_attachments'][$name] = $old['_attachments'][$name];
}
// load photo.attachment from old Kolab2 format to be directly embedded in xcard block
if ($name == 'photo.attachment' && !isset($object['photo']) && !$object['_attachments'][$name]['content'] && $att['key']) {
$object['photo'] = $this->get_attachment($object['_msguid'], $att['key'], $object['_mailbox']);
unset($object['_attachments'][$name]);
}
}
}
if ($raw_msg = $this->build_message($object, $type)) {
$result = $this->imap->save_message($this->name, $raw_msg, '', false);
// delete old message
if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) {
$this->imap->delete_message($object['_msguid'], $object['_mailbox']);
}
else if ($result && $uid && ($msguid = $this->uid2msguid($uid))) {
$this->imap->delete_message($msguid, $this->name);
}
// TODO: update cache with new UID
$this->uid2msg[$object['uid']] = $result;
}
return $result;
}
/**
* Delete the specified object from this folder.
*
* @param mixed $object The Kolab object to delete or object UID
* @param boolean $expunge Should the folder be expunged?
* @param boolean $trigger Should the folder update be triggered?
*
* @return boolean True if successful, false on error
*/
public function delete($object, $expunge = true, $trigger = true)
{
$msguid = is_array($object) ? $object['_msguid'] : $this->uid2msguid($object);
if ($msguid && $expunge) {
return $this->imap->delete_message($msguid, $this->name);
}
else if ($msguid) {
return $this->imap->set_flag($msguid, 'DELETED', $this->name);
}
return false;
}
/**
*
*/
public function delete_all()
{
return $this->imap->clear_folder($this->name);
}
/**
* Restore a previously deleted object
*
* @param string Object UID
* @return mixed Message UID on success, false on error
*/
public function undelete($uid)
{
if ($msguid = $this->uid2msguid($uid, true)) {
if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) {
return $msguid;
}
}
return false;
}
/**
* Move a Kolab object message to another IMAP folder
*
* @param string Object UID
* @param string IMAP folder to move object to
* @return boolean True on success, false on failure
*/
public function move($uid, $target_folder)
{
if ($msguid = $this->uid2msguid($uid)) {
if ($success = $this->imap->move_message($msguid, $target_folder, $this->name)) {
// TODO: update cache
return true;
}
else {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to move message $msguid to $target_folder: " . $this->imap->get_error_str(),
), true);
}
}
return false;
}
/**
* Resolve an object UID into an IMAP message UID
*/
private function uid2msguid($uid, $deleted = false)
{
if (!isset($this->uid2msg[$uid])) {
// use IMAP SEARCH to get the right message
$index = $this->imap->search_once($this->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid);
$results = $index->get();
$this->uid2msg[$uid] = $results[0];
// TODO: cache this lookup
}
return $this->uid2msg[$uid];
}
/**
* Creates source of the configuration object message
*/
private function build_message(&$object, $type)
{
// load old object to preserve data we don't understand/process
if (is_object($object['_formatobj']))
$format = $object['_formatobj'];
else if ($object['_msguid'] && ($old = $this->read_object($object['_msguid'], $type, $object['_mailbox'])))
$format = $old['_formatobj'];
// create new kolab_format instance
if (!$format)
$format = kolab_format::factory($type);
$format->set($object);
$xml = $format->write();
$object['uid'] = $format->uid; // get read UID from format
if (!$format->is_valid() || empty($object['uid'])) {
return false;
}
$mime = new Mail_mime("\r\n");
$rcmail = rcmail::get_instance();
$headers = array();
if ($ident = $rcmail->user->get_identity()) {
$headers['From'] = $ident['email'];
$headers['To'] = $ident['email'];
}
$headers['Date'] = date('r');
$headers['X-Kolab-Type'] = self::KTYPE_PREFIX . $type;
$headers['Subject'] = $object['uid'];
// $headers['Message-ID'] = rcmail_gen_message_id();
$headers['User-Agent'] = $rcmail->config->get('useragent');
$mime->headers($headers);
$mime->setTXTBody('This is a Kolab Groupware object. '
. 'To view this object you will need an email client that understands the Kolab Groupware format. '
. "For a list of such email clients please visit http://www.kolab.org/\n\n");
$mime->addAttachment($xml,
$format->CTYPE,
'kolab.xml',
false, '8bit', 'attachment', RCMAIL_CHARSET, '', '',
$rcmail->config->get('mime_param_folding') ? 'quoted-printable' : null,
$rcmail->config->get('mime_param_folding') == 2 ? 'quoted-printable' : null,
'', RCMAIL_CHARSET
);
// save object attachments as separate parts
// TODO: optimize memory consumption by using tempfiles for transfer
foreach ((array)$object['_attachments'] as $name => $att) {
if (empty($att['content']) && !empty($att['key'])) {
$msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid'];
$att['content'] = $this->get_attachment($msguid, $att['key'], $object['_mailbox']);
}
if (!empty($att['content'])) {
$mime->addAttachment($att['content'], $att['type'], $name, false);
}
}
return $mime->getMessage();
}
/**
* Triggers any required updates after changes within the
* folder. This is currently only required for handling free/busy
* information with Kolab.
*
* @return boolean|PEAR_Error True if successfull.
*/
public function trigger()
{
$owner = $this->get_owner();
$result = false;
switch($this->type) {
case 'event':
if ($this->get_namespace() == 'personal') {
$result = $this->trigger_url(
sprintf('%s/trigger/%s/%s.pfb', kolab_storage::get_freebusy_server(), $owner, $this->imap->mod_folder($this->name)),
$this->imap->options['user'],
$this->imap->options['password']
);
}
break;
default:
return true;
}
if ($result && is_a($result, 'PEAR_Error')) {
return PEAR::raiseError(sprintf("Failed triggering folder %s. Error was: %s",
$this->name, $result->getMessage()));
}
return $result;
}
/**
* Triggers a URL.
*
* @param string $url The URL to be triggered.
* @param string $auth_user Username to authenticate with
* @param string $auth_passwd Password for basic auth
* @return boolean|PEAR_Error True if successfull.
*/
private function trigger_url($url, $auth_user = null, $auth_passwd = null)
{
require_once('HTTP/Request.php');
$request = new HTTP_Request($url);
// set authentication credentials
if ($auth_user && $auth_passwd)
$request->setBasicAuth($auth_user, $auth_passwd);
$result = $request->sendRequest(true);
// rcube::write_log('trigger', $request->getResponseBody());
return $result;
}
/* Legacy methods to keep compatibility with the old Horde Kolab_Storage classes */
/**
* Compatibility method
*/
public function getOwner()
{
console("Call to deprecated method kolab_storage_folder::getOwner()");
return $this->get_owner();
}
/**
* Get IMAP ACL information for this folder
*/
public function getMyRights()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getMyRights()");
return $this->get_myrights();
}
/**
* NOP to stay compatible with the formerly used Horde classes
*/
public function getData()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getData()");
return $this;
}
/**
* List all Kolab objects of the given type
*/
public function getObjects($type = null)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getObjects()");
return $this->get_objects($type);
}
/**
* Getter for a single Kolab object, identified by its UID
*/
public function getObject($uid)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getObject()");
return $this->get_object($uid);
}
/**
*
*/
public function getAttachment($key)
{
PEAR::raiseError("Call to deprecated method not returning anything.");
return null;
}
/**
* Alias function of delete()
*/
public function deleteMessage($id, $trigger = true, $expunge = true)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::deleteMessage()");
return $this->delete(array('_msguid' => $id), $trigger, $expunge);
}
/**
*
*/
public function deleteAll()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::deleteAll()");
return $this->delete_all();
}
}
diff --git a/plugins/owncloud/owncloud.php b/plugins/owncloud/owncloud.php
index 2b05f3fc..d847cc3d 100644
--- a/plugins/owncloud/owncloud.php
+++ b/plugins/owncloud/owncloud.php
@@ -1,89 +1,84 @@
<?php
/**
* OwnCloud Plugin
*
* @author Aleksander 'A.L.E.C' Machniak <machniak@kolabsys.com>
* @licence GNU AGPL
*
* Configuration (see config.inc.php.dist)
*
*/
class owncloud extends rcube_plugin
{
// all task excluding 'login' and 'logout'
public $task = '?(?!login|logout).*';
// we've got no ajax handlers
public $noajax = true;
// skip frames
public $noframe = true;
function init()
{
$rcmail = rcmail::get_instance();
// requires kolab_auth plugin
if (empty($_SESSION['kolab_uid'])) {
return;
}
$this->add_texts('localization/', false);
// register task
$this->register_task('owncloud');
// register actions
$this->register_action('index', array($this, 'action'));
$this->register_action('redirect', array($this, 'redirect'));
// add taskbar button
$this->add_button(array(
- 'name' => 'owncloud',
+ 'command' => 'owncloud',
'class' => 'button-owncloud',
+ 'classsel' => 'button-owncloud button-selected',
+ 'innerclass' => 'button-inner',
'label' => 'owncloud.owncloud',
- 'href' => './?_task=owncloud',
- 'onclick' => sprintf("return %s.command('owncloud')", JS_OBJECT_NAME)
), 'taskbar');
- $rcmail->output->add_script(
- JS_OBJECT_NAME . ".enable_command('owncloud', true);\n" .
- JS_OBJECT_NAME . ".owncloud = function () { location.href = './?_task=owncloud'; }",
- 'head');
-
$skin = $rcmail->config->get('skin');
if (!file_exists($this->home."/skins/$skin/owncloud.css")) {
$skin = 'default';
}
// add style for taskbar button (must be here) and Help UI
$this->include_stylesheet("skins/$skin/owncloud.css");
}
function action()
{
$rcmail = rcmail::get_instance();
$rcmail->output->add_handlers(array('owncloudframe' => array($this, 'frame')));
$rcmail->output->set_pagetitle($this->gettext('owncloud'));
$rcmail->output->send('owncloud.owncloud');
}
function frame()
{
$rcmail = rcmail::get_instance();
$this->load_config();
$src = $rcmail->config->get('owncloud_url');
$user = $_SESSION['kolab_uid']; // requires kolab_auth plugin
$pass = $rcmail->decrypt($_SESSION['password']);
$src = preg_replace('/^(https?:\/\/)/',
'\\1' . urlencode($user) . ':' . urlencode($pass) . '@', $src);
return '<iframe id="owncloudframe" width="100%" height="100%" frameborder="0"'
.' src="' . $src. '"></iframe>';
}
}
diff --git a/plugins/owncloud/skins/larry/cloud.png b/plugins/owncloud/skins/larry/cloud.png
new file mode 100644
index 00000000..7ad3cd96
Binary files /dev/null and b/plugins/owncloud/skins/larry/cloud.png differ
diff --git a/plugins/owncloud/skins/larry/owncloud.css b/plugins/owncloud/skins/larry/owncloud.css
new file mode 100644
index 00000000..1e9aa891
--- /dev/null
+++ b/plugins/owncloud/skins/larry/owncloud.css
@@ -0,0 +1,14 @@
+/***** ownCloud plugin styles *****/
+
+#taskbar a.button-owncloud span.button-inner
+{
+ background: url(cloud.png) 5px 5px no-repeat;
+ height: 14px;
+}
+
+#taskbar a.button-owncloud:hover span.button-inner,
+#taskbar a.button-owncloud.button-selected span.button-inner
+{
+ background: url(cloud.png) 5px -16px no-repeat;
+ height: 14px;
+}
diff --git a/plugins/owncloud/skins/larry/templates/owncloud.html b/plugins/owncloud/skins/larry/templates/owncloud.html
new file mode 100644
index 00000000..8b3f470a
--- /dev/null
+++ b/plugins/owncloud/skins/larry/templates/owncloud.html
@@ -0,0 +1,20 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/this/owncloud.css" />
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+</head>
+<body class="noscroll">
+
+<roundcube:include file="/includes/header.html" />
+
+<div id="mainscreen" class="offset uibox" style="overflow: hidden">
+ <roundcube:object name="owncloudframe" />
+</div>
+
+<roundcube:include file="/includes/footer.html" />
+
+</body>
+</html>

File Metadata

Mime Type
text/x-diff
Expires
Mon, Aug 17, 10:06 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1257904
Default Alt Text
(92 KB)

Event Timeline