1
0
Fork 0

ajout simpleCalDAV pour insérer les date des ateliers dans Nextcloud

This commit is contained in:
Daniel Tartavel 2019-11-27 22:57:21 +01:00
parent 152cda4234
commit 3d2377d651
13 changed files with 3836 additions and 1 deletions

106
CalDAVCalendar.php Normal file
View File

@ -0,0 +1,106 @@
<?php
/**
* CalDAVCalendar
*
* Copyright 2014 Michael Palm <palm.michael@gmx.de>
*
* This class represents an accsessible calendar on the server.
*
* I think the functions
* - getURL()
* - getDisplayName()
* - getCalendarID()
* - getRGBAcolor()
* - getRBGcolor()
* are pretty self-explanatory.
*
*
* getCTag() returns the ctag of the calendar.
* The ctag is an hash-value used to check, if the client is up to date. The ctag changes everytime
* someone changes something in the calendar. So, to check if anything happend since your last visit:
* just compare the ctags.
*
* getOrder() returns the order of the calendar in the list of calendars
*
*
* @package simpleCalDAV
*
*/
class CalDAVCalendar {
private $url;
private $displayname;
private $ctag;
private $calendar_id;
private $rgba_color;
private $rbg_color;
private $order;
function __construct ( $url, $displayname = null, $ctag = null, $calendar_id = null, $rbg_color = null, $order = null ) {
$this->url = $url;
$this->displayname = $displayname;
$this->ctag = $ctag;
$this->calendar_id = $calendar_id;
$this->rbg_color = $rbg_color;
$this->order = $order;
}
function __toString () {
return( '(URL: '.$this->url.' Ctag: '.$this->ctag.' Displayname: '.$this->displayname .')'. "\n" );
}
// Getters
function getURL () {
return $this->url;
}
function getDisplayName () {
return $this->displayname;
}
function getCTag () {
return $this->ctag;
}
function getCalendarID () {
return $this->calendar_id;
}
function getRBGcolor () {
return $this->rbg_color;
}
function getOrder () {
return $this->order;
}
// Setters
function setURL ( $url ) {
$this->url = $url;
}
function setDisplayName ( $displayname ) {
$this->displayname = $displayname;
}
function setCtag ( $ctag ) {
$this->ctag = $ctag;
}
function setCalendarID ( $calendar_id ) {
$this->calendar_id = $calendar_id;
}
function setRBGcolor ( $rbg_color ) {
$this->rbg_color = $rbg_color;
}
function setOrder ( $order ) {
$this->order = $order;
}
}
?>

1309
CalDAVClient.php Normal file

File diff suppressed because it is too large Load Diff

93
CalDAVException.php Normal file
View File

@ -0,0 +1,93 @@
<?php
/**
* CalDAVException
*
* Copyright 2014 Michael Palm <palm.michael@gmx.de>
*
* This class is an extension to the Exception-class, to store and report additional data in the case
* of a problem.
* For debugging purposes, just sorround all of your SimpleCalDAVClient-Code with try { ... } catch (Exception $e) { echo $e->__toString(); }
*
* @package simpleCalDAV
*
*/
class CalDAVException extends Exception {
private $requestHeader;
private $requestBody;
private $responseHeader;
private $responseBody;
public function __construct($message, $client, $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->requestHeader = $client->GetHttpRequest();
$this->requestBody = $client->GetBody();
$this->responseHeader = $client->GetResponseHeaders();
$this->responseBody = $client->GetResponseBody();
}
public function __toString() {
$string = '';
$dom = new DOMDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->formatOutput = TRUE;
$string .= '<pre>';
$string .= 'Exception: '.$this->getMessage().'<br><br><br><br>';
$string .= 'If you think there is a bug in SimpleCalDAV, please report the following information on github or send it at palm.michael@gmx.de.<br><br><br>';
$string .= '<br>For debugging purposes:<br>';
$string .= '<br>last request:<br><br>';
$string .= $this->requestHeader;
if(!empty($this->requestBody)) {
if(!preg_match( '#^Content-type:.*?text/calendar.*?$#', $this->requestHeader, $matches)) {
$dom->loadXML($this->requestBody);
$string .= htmlentities($dom->saveXml());
}
else $string .= htmlentities($this->requestBody).'<br><br>';
}
$string .= '<br>last response:<br><br>';
$string .= $this->responseHeader;
if(!empty($this->responseBody)) {
if(!preg_match( '#^Content-type:.*?text/calendar.*?$#', $this->responseHeader, $matches)) {
$dom->loadXML($this->responseBody);
$string .= htmlentities($dom->saveXml());
}
else $string .= htmlentities($this->responseBody);
}
$string .= '<br><br>';
$string .= 'Trace:<br><br>'.$this->getTraceAsString();
$string .= '</pre>';
return $string;
}
public function getRequestHeader() {
return $this->requestHeader;
}
public function getrequestBody() {
return $this->requestBody;
}
public function getResponseHeader() {
return $this->responseHeader;
}
public function getresponseBody() {
return $this->responseBody;
}
}
?>

151
CalDAVFilter.php Normal file
View File

@ -0,0 +1,151 @@
<?php
/**
* CalDAVFilter
*
* Copyright 2014 Michael Palm <palm.michael@gmx.de>
*
* This class represents a filter, which can be used to get a custom report of
* calendar resources (events, todos, etc.) from the CalDAV-Server
*
* resourceType: The type of resource you want to get. Has to be either
* "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY" or "VALARM".
* You have to decide.
*
* mustInclude(),
* mustIncludeMatchSubstr(),
* mustOverlapWithTimerange(): Use these functions for further filter-options
*
* toXML(): Transforms the filter to xml-code for the server. Used to pass as
* argument for SimpleCalDAVClient->getCustomReport()
*
* @package simpleCalDAV
*
*/
class CalDAVFilter {
private $resourceType;
private $mustIncludes = array();
/*
* @param $type The type of resource you want to get. Has to be either
* "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY" or "VALARM".
* You have to decide.
*/
public function __construct ( $type ) {
$this->resourceType = $type;
}
/**
* function mustInclude()
* Specifies that a certin property has to be included. The content of the
* property is irrelevant.
*
* Only call this function and mustIncludeMatchSubstr() once per property!
*
* Examples:
* mustInclude("SUMMARY"); specifies that all returned resources have to
* have the SUMMARY-property.
* mustInclude("LOCATION "); specifies that all returned resources have to
* have the LOCATION-property.
*
* Arguments:
* @param $field The name of the property. For a full list of valid
* property names see http://www.rfcreader.com/#rfc5545_line3622
* Note that the server might not support all of them.
* @param $inverse Makes the effect inverse: The resource must NOT include
* the property $field
*/
public function mustInclude ( $field, $inverse = FALSE ) {
$this->mustIncludes[] = array("mustInclude", $field, $inverse);
}
/**
* function mustIncludeMatchSubstr()
* Specifies that a certin property has to be included and that its value
* has to match a given substring.
*
* Only call this function and mustInclude() once per property!
*
* Examples:
* mustIncludeMatchSubstr("SUMMARY", "a part of the summary"); would return
* a resource with "SUMMARY:This is a part of the summary" included, but no
* resource with "SUMMARY:This is a part of the".
*
* Arguments:
* @param $field The name of the property. For a full list of valid
* property names see http://www.rfcreader.com/#rfc5545_line3622
* Note that the server might not support all of them.
* @param $substring Substring to match against the value of the property.
* @param $inverse Makes the effect inverse: The property value must NOT
* include the $substring
*/
public function mustIncludeMatchSubstr ( $field, $substring, $inverse = FALSE ) {
$this->mustIncludes[] = array("mustIncludeMatchSubstr", $field, $substring, $inverse);
}
/**
* function mustOverlapWithTimerange()
* Specifies that the resource has to overlap with a given timerange.
* @see http://www.rfcreader.com/#rfc4791_line3944
*
* Only call this function once per CalDAVFilter-object!
*
* Arguments:
* @param $start The starting point of the time interval. Must be in the format yyyymmddThhmmssZ and should be in
* GMT. If omitted the value is set to -infinity.
* @param $end The end point of the time interval. Must be in the format yyyymmddThhmmssZ and should be in
* GMT. If omitted the value is set to +infinity.
*/
public function mustOverlapWithTimerange ( $start = NULL, $end = NULL) {
// Are $start and $end in the correct format?
if ( ( isset($start) and ! preg_match( '#^\d\d\d\d\d\d\d\dT\d\d\d\d\d\dZ$#', $start, $matches ) )
or ( isset($end) and ! preg_match( '#^\d\d\d\d\d\d\d\dT\d\d\d\d\d\dZ$#', $end, $matches ) ) )
{ trigger_error('$start or $end are in the wrong format. They must have the format yyyymmddThhmmssZ and should be in GMT', E_USER_ERROR); }
$this->mustIncludes[] = array("mustOverlapWithTimerange", $start, $end);
}
/**
* Transforms the filter to xml-code for the server. Used to pass as
* argument for SimpleCalDAVClient->getCustomReport()
*
* Example:
* $simpleCalDAVClient->getCustomReport($filter->toXML());
*
* @see SimpleCalDAVClient.php
*/
public function toXML () {
$xml = '<C:comp-filter name="VCALENDAR"><C:comp-filter name="'.$this->resourceType.'">';
foreach($this->mustIncludes as $filter) {
switch($filter[0]) {
case "mustInclude":
$xml .= '<C:prop-filter name="'.$filter[1].'"';
if(!$filter[2]) $xml .= '/>';
else $xml .= '><C:is-not-defined/></C:prop-filter>';
break;
case "mustIncludeMatchSubstr":
$xml .= '<C:prop-filter name="'.$filter[1].'"><C:text-match';
if($filter[3]) $xml .= ' negate-condition="yes"';
$xml .= '>'.$filter[2].'</C:text-match></C:prop-filter>';
break;
case "mustOverlapWithTimerange":
if($this->resourceType == "VTODO") $xml .= '<C:comp-filter name="VALARM">';
$xml .= '<C:time-range';
if($filter[1] != NULL) $xml .= ' start="'.$filter[1].'"';
if($filter[2] != NULL) $xml .= ' end="'.$filter[2].'"';
$xml .= '/>';
if($this->resourceType == "VTODO") $xml .= '</C:comp-filter>';
break;
}
}
$xml .= '</C:comp-filter></C:comp-filter>';
return $xml;
}
}
?>

51
CalDAVObject.php Normal file
View File

@ -0,0 +1,51 @@
<?php
/**
* CalDAVObject
*
* Copyright 2014 Michael Palm <palm.michael@gmx.de>
*
* This class represents a calendar resource on the CalDAV-Server (event, todo, etc.)
*
* href: The link to the resource in the calendar
* data: The iCalendar-Data. The "heart" of the resource.
* etag: The entity tag is a unique identifier, not only of a resource
* like the unique ID, but of serveral versions of the same resource. This means that a resource with one unique
* ID can have many different entity tags, depending on the content of the resource. One version of a resource,
* i. e. one special description in combination with one special starting time, created at one specific time,
* etc., has exactly on unique entity tag.
* The assignment of an entity tag ensures, that you know what you are changing/deleting. It ensures, that no one
* changed the resource between your viewing of the resource and your change/delete-request. Assigning an entity tag
* provides you of accidently destroying the work of others.
*
* @package simpleCalDAV
*
*/
class CalDAVObject {
private $href;
private $data;
private $etag;
public function __construct ($href, $data, $etag) {
$this->href = $href;
$this->data = $data;
$this->etag = $etag;
}
// Getter
public function getHref () {
return $this->href;
}
public function getData () {
return $this->data;
}
public function getEtag () {
return $this->etag;
}
}
?>

View File

@ -1,3 +1,3 @@
# gestion_ateliers
programmes permettant de simplifier et automatiser la gestion des ateliers dans dolibarr et les calendrier nextcloud
programmes permettant de simplifier et automatiser la gestion des ateliers dans dolibarr et les calendriers nextcloud

415
SimpleCalDAVClient.php Normal file
View File

@ -0,0 +1,415 @@
<?php
/**
* SimpleCalDAVClient
*
* Copyright 2014 Michael Palm <palm.michael@gmx.de>
*
* simpleCalDAV is a php library that allows you to connect to a calDAV-server to get event-, todo-
* and free/busy-calendar resources from the server, to change them, to delete them, to create new ones, etc.
* simpleCalDAV was made and tested for connections to the CalDAV-server Baikal 0.2.7. But it should work
* with any other CalDAV-server too.
*
* It contains the following functions:
* - connect()
* - findCalendars()
* - setCalendar()
* - create()
* - change()
* - delete()
* - getEvents()
* - getTODOs()
* - getCustomReport()
*
* All of those functions - except the last one - are realy easy to use, self-explanatory and are
* deliverd with a big innitial comment, which explains all needed arguments and the return values.
*
* This library is heavily based on AgenDAV caldav-client-v2.php by Jorge López Pérez <jorge@adobo.org> which
* again is heavily based on DAViCal caldav-client-v2.php by Andrew McMillan <andrew@mcmillan.net.nz>.
* Actually, I hardly added any features. The main point of my work is to make everything straight
* forward and easy to use. You can use simpleCalDAV whithout a deeper understanding of the
* calDAV-protocol.
*
* Requirements of this library are
* - The php extension cURL ( http://www.php.net/manual/en/book.curl.php )
* - From Andrews Web Libraries: ( https://github.com/andrews-web-libraries/awl )
* - XMLDocument.php
* - XMLElement.php
* - AWLUtilities.php
*
* @package simpleCalDAV
*/
require_once('CalDAVClient.php');
require_once('CalDAVException.php');
require_once('CalDAVFilter.php');
require_once('CalDAVObject.php');
class SimpleCalDAVClient {
private $client;
private $url;
/**
* function connect()
* Connects to a CalDAV-Server.
*
* Arguments:
* @param $url URL to the CalDAV-server. E.g. http://exam.pl/baikal/cal.php/username/calendername/
* @param $user Username to login with
* @param $pass Password to login with
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); }
*/
function connect ( $url, $user, $pass )
{
// Connect to CalDAV-Server and log in
$client = new CalDAVClient($url, $user, $pass);
// Valid CalDAV-Server? Or is it just a WebDAV-Server?
if( ! $client->isValidCalDAVServer() )
{
if( $client->GetHttpResultCode() == '401' ) // unauthorisized
{
throw new CalDAVException('Login failed', $client);
}
elseif( $client->GetHttpResultCode() == '' ) // can't reach server
{
throw new CalDAVException('Can\'t reach server', $client);
}
else throw new CalDAVException('Could\'n find a CalDAV-collection under the url', $client);
}
// Check for errors
if( $client->GetHttpResultCode() != '200' ) {
if( $client->GetHttpResultCode() == '401' ) // unauthorisized
{
throw new CalDAVException('Login failed', $client);
}
elseif( $client->GetHttpResultCode() == '' ) // can't reach server
{
throw new CalDAVException('Can\'t reach server', $client);
}
else // Unknown status
{
throw new CalDAVException('Recieved unknown HTTP status while checking the connection after establishing it', $client);
}
}
$this->client = $client;
}
/**
* function findCalendars()
*
* Requests a list of all accessible calendars on the server
*
* Return value:
* @return an array of CalDAVCalendar-Objects (see CalDAVCalendar.php), representing all calendars accessible by the current principal (user).
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function findCalendars()
{
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
return $this->client->FindCalendars(true);
}
/**
* function setCalendar()
*
* Sets the actual calendar to work with
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function setCalendar ( CalDAVCalendar $calendar )
{
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
$this->client->SetCalendar($this->client->first_url_part.$calendar->getURL());
// Is there a '/' at the end of the calendar_url?
if ( ! preg_match( '#^.*?/$#', $this->client->calendar_url, $matches ) ) { $this->url = $this->client->calendar_url.'/'; }
else { $this->url = $this->client->calendar_url; }
}
/**
* function create()
* Creates a new calendar resource on the CalDAV-Server (event, todo, etc.).
*
* Arguments:
* @param $cal iCalendar-data of the resource you want to create.
* Notice: The iCalendar-data contains the unique ID which specifies where the event is being saved.
*
* Return value:
* @return An CalDAVObject-representation (see CalDAVObject.php) of your created resource
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function create ( $cal )
{
// Connection and calendar set?
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
if(!isset($this->client->calendar_url)) throw new Exception('No calendar selected. Try findCalendars() and setCalendar().');
// Parse $cal for UID
if (! preg_match( '#^UID:(.*?)\r?\n?$#m', $cal, $matches ) ) { throw new Exception('Can\'t find UID in $cal'); }
else { $uid = $matches[1]; }
// Does $this->url.$uid.'.ics' already exist?
$result = $this->client->GetEntryByHref( $this->url.$uid.'.ics' );
if ( $this->client->GetHttpResultCode() == '200' ) { throw new CalDAVException($this->url.$uid.'.ics already exists. UID not unique?', $this->client); }
else if ( $this->client->GetHttpResultCode() == '404' );
else throw new CalDAVException('Recieved unknown HTTP status', $this->client);
// Put it!
$newEtag = $this->client->DoPUTRequest( $this->url.$uid.'.ics', $cal );
// PUT-request successfull?
if ( $this->client->GetHttpResultCode() != '201' )
{
if ( $this->client->GetHttpResultCode() == '204' ) // $url.$uid.'.ics' already existed on server
{
throw new CalDAVException( $this->url.$uid.'.ics already existed. Entry has been overwritten.', $this->client);
}
else // Unknown status
{
throw new CalDAVException('Recieved unknown HTTP status', $this->client);
}
}
return new CalDAVObject($this->url.$uid.'.ics', $cal, $newEtag);
}
/**
* function change()
* Changes a calendar resource (event, todo, etc.) on the CalDAV-Server.
*
* Arguments:
* @param $href See CalDAVObject.php
* @param $cal The new iCalendar-data that should be used to overwrite the old one.
* @param $etag See CalDAVObject.php
*
* Return value:
* @return An CalDAVObject-representation (see CalDAVObject.php) of your changed resource
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function change ( $href, $new_data, $etag )
{
// Connection and calendar set?
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
if(!isset($this->client->calendar_url)) throw new Exception('No calendar selected. Try findCalendars() and setCalendar().');
// Does $href exist?
$result = $this->client->GetEntryByHref($href);
if ( $this->client->GetHttpResultCode() == '200' );
else if ( $this->client->GetHttpResultCode() == '404' ) throw new CalDAVException('Can\'t find '.$href.' on the server', $this->client);
else throw new CalDAVException('Recieved unknown HTTP status', $this->client);
// $etag correct?
if($result[0]['etag'] != $etag) { throw new CalDAVException('Wrong entity tag. The entity seems to have changed.', $this->client); }
// Put it!
$newEtag = $this->client->DoPUTRequest( $href, $new_data, $etag );
// PUT-request successfull?
if ( $this->client->GetHttpResultCode() != '204' && $this->client->GetHttpResultCode() != '200' )
{
throw new CalDAVException('Recieved unknown HTTP status', $this->client);
}
return new CalDAVObject($href, $new_data, $newEtag);
}
/**
* function delete()
* Delets an event or a TODO from the CalDAV-Server.
*
* Arguments:
* @param $href See CalDAVObject.php
* @param $etag See CalDAVObject.php
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function delete ( $href, $etag )
{
// Connection and calendar set?
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
if(!isset($this->client->calendar_url)) throw new Exception('No calendar selected. Try findCalendars() and setCalendar().');
// Does $href exist?
$result = $this->client->GetEntryByHref($href);
if(count($result) == 0) throw new CalDAVException('Can\'t find '.$href.'on server', $this->client);
// $etag correct?
if($result[0]['etag'] != $etag) { throw new CalDAVException('Wrong entity tag. The entity seems to have changed.', $this->client); }
// Do the deletion
$this->client->DoDELETERequest($href, $etag);
// Deletion successfull?
if ( $this->client->GetHttpResultCode() != '200' and $this->client->GetHttpResultCode() != '204' )
{
throw new CalDAVException('Recieved unknown HTTP status', $this->client);
}
}
/**
* function getEvents()
* Gets a all events from the CalDAV-Server which lie in a defined time interval.
*
* Arguments:
* @param $start The starting point of the time interval. Must be in the format yyyymmddThhmmssZ and should be in
* GMT. If omitted the value is set to -infinity.
* @param $end The end point of the time interval. Must be in the format yyyymmddThhmmssZ and should be in
* GMT. If omitted the value is set to +infinity.
*
* Return value:
* @return an array of CalDAVObjects (See CalDAVObject.php), representing the found events.
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function getEvents ( $start = null, $end = null )
{
// Connection and calendar set?
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
if(!isset($this->client->calendar_url)) throw new Exception('No calendar selected. Try findCalendars() and setCalendar().');
// Are $start and $end in the correct format?
if ( ( isset($start) and ! preg_match( '#^\d\d\d\d\d\d\d\dT\d\d\d\d\d\dZ$#', $start, $matches ) )
or ( isset($end) and ! preg_match( '#^\d\d\d\d\d\d\d\dT\d\d\d\d\d\dZ$#', $end, $matches ) ) )
{ trigger_error('$start or $end are in the wrong format. They must have the format yyyymmddThhmmssZ and should be in GMT', E_USER_ERROR); }
// Get it!
$results = $this->client->GetEvents( $start, $end );
// GET-request successfull?
if ( $this->client->GetHttpResultCode() != '207' )
{
throw new CalDAVException('Recieved unknown HTTP status', $this->client);
}
// Reformat
$report = array();
foreach($results as $event) $report[] = new CalDAVObject($this->url.$event['href'], $event['data'], $event['etag']);
return $report;
}
/**
* function getTODOs()
* Gets a all TODOs from the CalDAV-Server which lie in a defined time interval and match the
* given criteria.
*
* Arguments:
* @param $start The starting point of the time interval. Must be in the format yyyymmddThhmmssZ and should be in
* GMT. If omitted the value is set to -infinity.
* @param $end The end point of the time interval. Must be in the format yyyymmddThhmmssZ and should be in
* GMT. If omitted the value is set to +infinity.
* @param $complete Filter for completed tasks (true) or for uncompleted tasks (false). If omitted, the function will return both.
* @param $cancelled Filter for cancelled tasks (true) or for uncancelled tasks (false). If omitted, the function will return both.
*
* Return value:
* @return an array of CalDAVObjects (See CalDAVObject.php), representing the found TODOs.
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function getTODOs ( $start = null, $end = null, $completed = null, $cancelled = null )
{
// Connection and calendar set?
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
if(!isset($this->client->calendar_url)) throw new Exception('No calendar selected. Try findCalendars() and setCalendar().');
// Are $start and $end in the correct format?
if ( ( isset($start) and ! preg_match( '#^\d\d\d\d\d\d\d\dT\d\d\d\d\d\dZ$#', $start, $matches ) )
or ( isset($end) and ! preg_match( '#^\d\d\d\d\d\d\d\dT\d\d\d\d\d\dZ$#', $end, $matches ) ) )
{ trigger_error('$start or $end are in the wrong format. They must have the format yyyymmddThhmmssZ and should be in GMT', E_USER_ERROR); }
// Get it!
$results = $this->client->GetTodos( $start, $end, $completed, $cancelled );
// GET-request successfull?
if ( $this->client->GetHttpResultCode() != '207' )
{
throw new CalDAVException('Recieved unknown HTTP status', $this->client);
}
// Reformat
$report = array();
foreach($results as $event) $report[] = new CalDAVObject($this->url.$event['href'], $event['data'], $event['etag']);
return $report;
}
/**
* function getCustomReport()
* Sends a custom request to the server
* (Sends a REPORT-request with a custom <C:filter>-tag)
*
* You can either write the filterXML yourself or build an CalDAVFilter-object (see CalDAVFilter.php).
*
* See http://www.rfcreader.com/#rfc4791_line1524 for more information about how to write filters on your own.
*
* Arguments:
* @param $filterXML The stuff, you want to send encapsulated in the <C:filter>-tag.
*
* Return value:
* @return an array of CalDAVObjects (See CalDAVObject.php), representing the found calendar resources.
*
* Debugging:
* @throws CalDAVException
* For debugging purposes, just sorround everything with try { ... } catch (Exception $e) { echo $e->__toString(); exit(-1); }
*/
function getCustomReport ( $filterXML )
{
// Connection and calendar set?
if(!isset($this->client)) throw new Exception('No connection. Try connect().');
if(!isset($this->client->calendar_url)) throw new Exception('No calendar selected. Try findCalendars() and setCalendar().');
// Get report!
$this->client->SetDepth('1');
// Get it!
$results = $this->client->DoCalendarQuery('<C:filter>'.$filterXML.'</C:filter>');
// GET-request successfull?
if ( $this->client->GetHttpResultCode() != '207' )
{
throw new CalDAVException('Recieved unknown HTTP status', $this->client);
}
// Reformat
$report = array();
foreach($results as $event) $report[] = new CalDAVObject($this->url.$event['href'], $event['data'], $event['etag']);
return $report;
}
}
?>

62
config/listCalendars.php Normal file
View File

@ -0,0 +1,62 @@
<?php
/**
* listCalendars
*
* Copyright 2014 Michael Palm <palm.michael@gmx.de>
*
* Open this file in a webbrowser to view a list of all accessible calendars
* on the server and the information related to those calendars. It can be used
* to determine the calendar-id, needed for SimpleCalDAV.
*
* @package simpleCalDAV
*/
require_once('../SimpleCalDAVClient.php');
if($_POST == null) {
echo '
<form action="#" method="post">
<p>This formular can be used to view a list of all accessible calendars on the server and the information related to those calendars. It can be used to determine the calendar-id, needed for SimpleCalDAV.</p>
<p>Calendar-URL:<br><input name="url" type="text" size="30" maxlength="100"></p>
<p>Username:<br><input name="user" type="text" size="30" maxlength="100"></p>
<p>Password:<br><input name="pass" type="text" size="30" maxlength="100"></p>
<input type="submit" value=" Show! ">
</form>';
}
else {
$client = new SimpleCalDAVClient();
try {
$client->connect($_POST['url'], $_POST['user'], $_POST['pass']);
$calendars = $client->findCalendars();
echo'
<table>';
$i = 0;
foreach($calendars as $cal) {
$i++;
echo '
<tr> <td></td> <td><strong>Calendar #'.$i.'</strong></td> </tr>
<tr> <td>URL:</td> <td>'.$cal->getURL().'</td> </tr>
<tr> <td>Display Name:</td> <td>'.$cal->getDisplayName().'</td> </tr>
<tr> <td>Calendar ID:</td> <td>'.$cal->getCalendarID().'</td> </tr>
<tr> <td>CTAG:</td> <td>'.$cal->getCTag().'</td> </tr>
<tr> <td>RBG-Color:</td> <td>'.$cal->getRBGcolor().'</td> </tr>
<tr> <td>Order:</td> <td>'.$cal->getOrder().'</td> </tr>
<tr> <td></td> <td></td> </tr>';
}
echo '
</table>';
}
catch (Exception $e) {
echo $e->__toString();
}
}
?>

691
include/AWLUtilities.php Normal file
View File

@ -0,0 +1,691 @@
<?php
/**
* Utility functions of a general nature which are used by
* most AWL library classes.
*
* @package awl
* @subpackage Utilities
* @author Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later
*/
if ( !function_exists('dbg_error_log') ) {
/**
* Writes a debug message into the error log using printf syntax. If the first
* parameter is "ERROR" then the message will _always_ be logged.
* Otherwise, the first parameter is a "component" name, and will only be logged
* if $c->dbg["component"] is set to some non-null value.
*
* If you want to see every log message then $c->dbg["ALL"] can be set, to
* override the debugging status of the individual components.
*
* @var string $component The component to identify itself, or "ERROR", or "LOG:component"
* @var string $format A format string for the log message
* @var [string $parameter ...] Parameters for the format string.
*/
function dbg_error_log() {
global $c;
$args = func_get_args();
$type = "DBG";
$component = array_shift($args);
if ( substr( $component, 0, 3) == "LOG" ) {
// Special escape case for stuff that always gets logged.
$type = 'LOG';
$component = substr($component,4);
}
else if ( $component == "ERROR" ) {
$type = "***";
}
else if ( isset($c->dbg["ALL"]) ) {
$type = "ALL";
}
else if ( !isset($c->dbg[strtolower($component)]) ) return;
$argc = func_num_args();
if ( 2 <= $argc ) {
$format = array_shift($args);
}
else {
$format = "%s";
}
@error_log( $c->sysabbr.": $type: $component:". vsprintf( $format, $args ) );
}
}
if ( !function_exists('fatal') ) {
function fatal() {
global $c;
$args = func_get_args();
$argc = func_num_args();
if ( 2 <= $argc ) {
$format = array_shift($args);
}
else {
$format = "%s";
}
@error_log( $c->sysabbr.": FATAL: $component:". vsprintf( $format, $args ) );
@error_log( "================= Stack Trace ===================" );
$trace = array_reverse(debug_backtrace());
array_pop($trace);
foreach( $trace AS $k => $v ) {
@error_log( sprintf(" ===> %s[%d] calls %s%s%s()",
$v['file'],
$v['line'],
(isset($v['class'])?$v['class']:''),
(isset($v['type'])?$v['type']:''),
(isset($v['function'])?$v['function']:'')
));
}
echo "Fatal Error";
exit();
}
}
if ( !function_exists('trace_bug') ) {
/**
* Not as sever as a fatal() call, but we want to log and trace it
*/
function trace_bug() {
global $c;
$args = func_get_args();
$argc = func_num_args();
if ( 2 <= $argc ) {
$format = array_shift($args);
}
else {
$format = "%s";
}
@error_log( $c->sysabbr.": BUG: $component:". vsprintf( $format, $args ) );
@error_log( "================= Stack Trace ===================" );
$trace = array_reverse(debug_backtrace());
array_pop($trace);
foreach( $trace AS $k => $v ) {
@error_log( sprintf(" ===> %s[%d] calls %s%s%s()",
$v['file'],
$v['line'],
(isset($v['class'])?$v['class']:''),
(isset($v['type'])?$v['type']:''),
(isset($v['function'])?$v['function']:'')
));
}
}
}
if ( !function_exists('apache_request_headers') ) {
/**
* Compatibility so we can use the apache function name and still work with CGI
* @package awl
*/
eval('
function apache_request_headers() {
foreach($_SERVER as $key=>$value) {
if (substr($key,0,5)=="HTTP_") {
$key=str_replace(" ","-",ucwords(strtolower(str_replace("_"," ",substr($key,5)))));
$out[$key]=$value;
}
}
return $out;
}
');
}
if ( !function_exists('dbg_log_array') ) {
/**
* Function to dump an array to the error log, possibly recursively
*
* @var string $component Which component should this log message identify itself from
* @var string $name What name should this array dump identify itself as
* @var array $arr The array to be dumped.
* @var boolean $recursive Should the dump recurse into arrays/objects in the array
*/
function dbg_log_array( $component, $name, $arr, $recursive = false ) {
if ( !isset($arr) || (gettype($arr) != 'array' && gettype($arr) != 'object') ) {
dbg_error_log( $component, "%s: array is not set, or is not an array!", $name);
return;
}
foreach ($arr as $key => $value) {
dbg_error_log( $component, "%s: >>%s<< = >>%s<<", $name, $key,
(gettype($value) == 'array' || gettype($value) == 'object' ? gettype($value) : $value) );
if ( $recursive && (gettype($value) == 'array' || (gettype($value) == 'object' && "$key" != 'self' && "$key" != 'parent') ) ) {
dbg_log_array( $component, "$name"."[$key]", $value, $recursive );
}
}
}
}
if ( !function_exists("session_simple_md5") ) {
/**
* Make a plain MD5 hash of a string, identifying the type of hash it is
*
* @param string $instr The string to be salted and MD5'd
* @return string The *MD5* and the MD5 of the string
*/
function session_simple_md5( $instr ) {
global $c;
if ( isset($c->dbg['password']) ) dbg_error_log( "Login", "Making plain MD5: instr=$instr, md5($instr)=".md5($instr) );
return ( '*MD5*'. md5($instr) );
}
}
if ( !function_exists("session_salted_md5") ) {
/**
* Make a salted MD5 string, given a string and (possibly) a salt.
*
* If no salt is supplied we will generate a random one.
*
* @param string $instr The string to be salted and MD5'd
* @param string $salt Some salt to sprinkle into the string to be MD5'd so we don't get the same PW always hashing to the same value.
* @return string The salt, a * and the MD5 of the salted string, as in SALT*SALTEDHASH
*/
function session_salted_md5( $instr, $salt = "" ) {
if ( $salt == "" ) $salt = substr( md5(rand(100000,999999)), 2, 8);
global $c;
if ( isset($c->dbg['password']) ) dbg_error_log( "Login", "Making salted MD5: salt=$salt, instr=$instr, md5($salt$instr)=".md5($salt . $instr) );
return ( sprintf("*%s*%s", $salt, md5($salt . $instr) ) );
}
}
if ( !function_exists("session_salted_sha1") ) {
/**
* Make a salted SHA1 string, given a string and (possibly) a salt. PHP5 only (although it
* could be made to work on PHP4 (@see http://www.openldap.org/faq/data/cache/347.html). The
* algorithm used here is compatible with OpenLDAP so passwords generated through this function
* should be able to be migrated to OpenLDAP by using the part following the second '*', i.e.
* the '{SSHA}....' part.
*
* If no salt is supplied we will generate a random one.
*
* @param string $instr The string to be salted and SHA1'd
* @param string $salt Some salt to sprinkle into the string to be SHA1'd so we don't get the same PW always hashing to the same value.
* @return string A *, the salt, a * and the SHA1 of the salted string, as in *SALT*SALTEDHASH
*/
function session_salted_sha1( $instr, $salt = "" ) {
if ( $salt == "" ) $salt = substr( str_replace('*','',base64_encode(sha1(rand(100000,9999999),true))), 2, 9);
global $c;
if ( isset($c->dbg['password']) ) dbg_error_log( "Login", "Making salted SHA1: salt=$salt, instr=$instr, encoded($instr$salt)=".base64_encode(sha1($instr . $salt, true).$salt) );
return ( sprintf("*%s*{SSHA}%s", $salt, base64_encode(sha1($instr.$salt, true) . $salt ) ) );
}
}
if ( !function_exists("session_validate_password") ) {
/**
* Checks what a user entered against the actual password on their account.
* @param string $they_sent What the user entered.
* @param string $we_have What we have in the database as their password. Which may (or may not) be a salted MD5.
* @return boolean Whether or not the users attempt matches what is already on file.
*/
function session_validate_password( $they_sent, $we_have ) {
global $c;
if ( preg_match('/^\*\*.+$/', $we_have ) ) {
// The "forced" style of "**plaintext" to allow easier admin setting
return ( "**$they_sent" == $we_have );
}
if ( isset($c->wp_includes) && substring($we_have,0,1) == '$' ) {
// Include Wordpress password handling, if it's in the path.
@require_once($c->wp_includes .'/class-phpass.php');
if ( class_exists('PasswordHash') ) {
$wp_hasher = new PasswordHash(8, true);
return $wp_hasher->CheckPassword($password, $hash);
}
}
if ( preg_match('/^\*(.+)\*{[A-Z]+}.+$/', $we_have, $regs ) ) {
if ( function_exists("session_salted_sha1") ) {
// A nicely salted sha1sum like "*<salt>*{SSHA}<salted_sha1>"
$salt = $regs[1];
$sha1_sent = session_salted_sha1( $they_sent, $salt ) ;
return ( $sha1_sent == $we_have );
}
else {
dbg_error_log( "ERROR", "Password is salted SHA-1 but you are using PHP4!" );
echo <<<EOERRMSG
<html>
<head>
<title>Salted SHA1 Password format not supported with PHP4</title>
</head>
<body>
<h1>Salted SHA1 Password format not supported with PHP4</h1>
<p>At some point you have used PHP5 to set the password for this user and now you are
using PHP4. You will need to assign a new password to this user using PHP4, or ensure
you use PHP5 everywhere (recommended).</p>
<p>AWL has now switched to using salted SHA-1 passwords by preference in a format
compatible with OpenLDAP.</p>
</body>
</html>
EOERRMSG;
exit;
}
}
if ( preg_match('/^\*MD5\*.+$/', $we_have, $regs ) ) {
// A crappy unsalted md5sum like "*MD5*<md5>"
$md5_sent = session_simple_md5( $they_sent ) ;
return ( $md5_sent == $we_have );
}
else if ( preg_match('/^\*(.+)\*.+$/', $we_have, $regs ) ) {
// A nicely salted md5sum like "*<salt>*<salted_md5>"
$salt = $regs[1];
$md5_sent = session_salted_md5( $they_sent, $salt ) ;
return ( $md5_sent == $we_have );
}
// Anything else is bad
return false;
}
}
if ( !function_exists("replace_uri_params") ) {
/**
* Given a URL (presumably the current one) and a parameter, replace the value of parameter,
* extending the URL as necessary if the parameter is not already there.
* @param string $uri The URI we will be replacing parameters in.
* @param array $replacements An array of replacement pairs array( "replace_this" => "with this" )
* @return string The URI with the replacements done.
*/
function replace_uri_params( $uri, $replacements ) {
$replaced = $uri;
foreach( $replacements AS $param => $new_value ) {
$rxp = preg_replace( '/([\[\]])/', '\\\\$1', $param ); // Some parameters may be arrays.
$regex = "/([&?])($rxp)=([^&]+)/";
dbg_error_log("core", "Looking for [%s] to replace with [%s] regex is %s and searching [%s]", $param, $new_value, $regex, $replaced );
if ( preg_match( $regex, $replaced ) )
$replaced = preg_replace( $regex, "\$1$param=$new_value", $replaced);
else
$replaced .= "&$param=$new_value";
}
if ( ! preg_match( '/\?/', $replaced ) ) {
$replaced = preg_replace("/&(.+)$/", "?\$1", $replaced);
}
$replaced = str_replace("&amp;", "--AmPeRsAnD--", $replaced);
$replaced = str_replace("&", "&amp;", $replaced);
$replaced = str_replace("--AmPeRsAnD--", "&amp;", $replaced);
dbg_error_log("core", "URI <<$uri>> morphed to <<$replaced>>");
return $replaced;
}
}
if ( !function_exists("uuid") ) {
/**
* Generates a Universally Unique IDentifier, version 4.
*
* RFC 4122 (http://www.ietf.org/rfc/rfc4122.txt) defines a special type of Globally
* Unique IDentifiers (GUID), as well as several methods for producing them. One
* such method, described in section 4.4, is based on truly random or pseudo-random
* number generators, and is therefore implementable in a language like PHP.
*
* We choose to produce pseudo-random numbers with the Mersenne Twister, and to always
* limit single generated numbers to 16 bits (ie. the decimal value 65535). That is
* because, even on 32-bit systems, PHP's RAND_MAX will often be the maximum *signed*
* value, with only the equivalent of 31 significant bits. Producing two 16-bit random
* numbers to make up a 32-bit one is less efficient, but guarantees that all 32 bits
* are random.
*
* The algorithm for version 4 UUIDs (ie. those based on random number generators)
* states that all 128 bits separated into the various fields (32 bits, 16 bits, 16 bits,
* 8 bits and 8 bits, 48 bits) should be random, except : (a) the version number should
* be the last 4 bits in the 3rd field, and (b) bits 6 and 7 of the 4th field should
* be 01. We try to conform to that definition as efficiently as possible, generating
* smaller values where possible, and minimizing the number of base conversions.
*
* @copyright Copyright (c) CFD Labs, 2006. This function may be used freely for
* any purpose ; it is distributed without any form of warranty whatsoever.
* @author David Holmes <dholmes@cfdsoftware.net>
*
* @return string A UUID, made up of 32 hex digits and 4 hyphens.
*/
function uuid() {
// The field names refer to RFC 4122 section 4.1.2
return sprintf('%04x%04x-%04x-%03x4-%04x-%04x%04x%04x',
mt_rand(0, 65535), mt_rand(0, 65535), // 32 bits for "time_low"
mt_rand(0, 65535), // 16 bits for "time_mid"
mt_rand(0, 4095), // 12 bits before the 0100 of (version) 4 for "time_hi_and_version"
bindec(substr_replace(sprintf('%016b', mt_rand(0, 65535)), '01', 6, 2)),
// 8 bits, the last two of which (positions 6 and 7) are 01, for "clk_seq_hi_res"
// (hence, the 2nd hex digit after the 3rd hyphen can only be 1, 5, 9 or d)
// 8 bits for "clk_seq_low"
mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535) // 48 bits for "node"
);
}
}
if ( !function_exists("translate") ) {
require("Translation.php");
}
if ( !function_exists("clone") && version_compare(phpversion(), '5.0') < 0) {
/**
* PHP5 screws with the assignment operator changing so that $a = $b means that
* $a becomes a reference to $b. There is a clone() that we can use in PHP5, so
* we have to emulate that for PHP4. Bleargh.
*/
eval( 'function clone($object) { return $object; }' );
}
if ( !function_exists("quoted_printable_encode") ) {
/**
* Process a string to fit the requirements of RFC2045 section 6.7. Note that
* this works, but replaces more characters than the minimum set. For readability
* the spaces aren't encoded as =20 though.
*/
function quoted_printable_encode($string) {
return preg_replace('/[^\r\n]{73}[^=\r\n]{2}/', "$0=\r\n", str_replace("%","=",str_replace("%20"," ",rawurlencode($string))));
}
}
if ( !function_exists("check_by_regex") ) {
/**
* Verify a value is OK by testing a regex against it. If it is an array apply it to
* each element in the array recursively. If it is an object we don't mess
* with it.
*/
function check_by_regex( $val, $regex ) {
if ( is_null($val) ) return null;
switch( $regex ) {
case 'int': $regex = '#^\d+$#'; break;
}
if ( is_array($val) ) {
foreach( $val AS $k => $v ) {
$val[$k] = check_by_regex($v,$regex);
}
}
else if ( ! is_object($val) ) {
if ( preg_match( $regex, $val, $matches) ) {
$val = $matches[0];
}
else {
$val = '';
}
}
return $val;
}
}
if ( !function_exists("param_to_global") ) {
/**
* Convert a parameter to a global. We first look in _POST and then in _GET,
* and if they passed in a bunch of valid characters, we will make sure the
* incoming is cleaned to only match that set.
*
* @param string $varname The name of the global variable to put the answer in
* @param string $match_regex The part of the parameter matching this regex will be returned
* @param string $alias1 An alias for the name that we should look for first.
* @param " ... More aliases, in the order which they should be examined. $varname will be appended to the end.
*/
function param_to_global( ) {
$args = func_get_args();
$varname = array_shift($args);
$GLOBALS[$varname] = null;
$match_regex = null;
$argc = func_num_args();
if ( $argc > 1 ) {
$match_regex = array_shift($args);
}
$args[] = $varname;
foreach( $args AS $k => $name ) {
if ( isset($_POST[$name]) ) {
$result = $_POST[$name];
break;
}
else if ( isset($_GET[$name]) ) {
$result = $_GET[$name];
break;
}
}
if ( !isset($result) ) return null;
if ( isset($match_regex) ) {
$result = check_by_regex( $result, $match_regex );
}
$GLOBALS[$varname] = $result;
return $result;
}
}
if ( !function_exists("get_fields") ) {
/**
* @var array $_AWL_field_cache is a cache of the field names for a table
*/
$_AWL_field_cache = array();
/**
* Get the names of the fields for a particular table
* @param string $tablename The name of the table.
* @return array of string The public fields in the table.
*/
function get_fields( $tablename ) {
global $_AWL_field_cache;
if ( !isset($_AWL_field_cache[$tablename]) ) {
dbg_error_log( "core", ":get_fields: Loading fields for table '$tablename'" );
$qry = new AwlQuery();
$db = $qry->GetConnection();
$qry->SetSQL($db->GetFields($tablename));
$qry->Exec("core");
$fields = array();
while( $row = $qry->Fetch() ) {
$fields[$row->fieldname] = $row->typename . ($row->precision >= 0 ? sprintf('(%d)',$row->precision) : '');
}
$_AWL_field_cache[$tablename] = $fields;
}
return $_AWL_field_cache[$tablename];
}
}
if ( !function_exists("force_utf8") ) {
function define_byte_mappings() {
global $byte_map, $nibble_good_chars;
# Needed for using Grant McLean's byte mappings code
$ascii_char = '[\x00-\x7F]';
$cont_byte = '[\x80-\xBF]';
$utf8_2 = '[\xC0-\xDF]' . $cont_byte;
$utf8_3 = '[\xE0-\xEF]' . $cont_byte . '{2}';
$utf8_4 = '[\xF0-\xF7]' . $cont_byte . '{3}';
$utf8_5 = '[\xF8-\xFB]' . $cont_byte . '{4}';
$nibble_good_chars = "/^($ascii_char+|$utf8_2|$utf8_3|$utf8_4|$utf8_5)(.*)$/s";
# From http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT
$byte_map = array(
"\x80" => "\xE2\x82\xAC", # EURO SIGN
"\x82" => "\xE2\x80\x9A", # SINGLE LOW-9 QUOTATION MARK
"\x83" => "\xC6\x92", # LATIN SMALL LETTER F WITH HOOK
"\x84" => "\xE2\x80\x9E", # DOUBLE LOW-9 QUOTATION MARK
"\x85" => "\xE2\x80\xA6", # HORIZONTAL ELLIPSIS
"\x86" => "\xE2\x80\xA0", # DAGGER
"\x87" => "\xE2\x80\xA1", # DOUBLE DAGGER
"\x88" => "\xCB\x86", # MODIFIER LETTER CIRCUMFLEX ACCENT
"\x89" => "\xE2\x80\xB0", # PER MILLE SIGN
"\x8A" => "\xC5\xA0", # LATIN CAPITAL LETTER S WITH CARON
"\x8B" => "\xE2\x80\xB9", # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
"\x8C" => "\xC5\x92", # LATIN CAPITAL LIGATURE OE
"\x8E" => "\xC5\xBD", # LATIN CAPITAL LETTER Z WITH CARON
"\x91" => "\xE2\x80\x98", # LEFT SINGLE QUOTATION MARK
"\x92" => "\xE2\x80\x99", # RIGHT SINGLE QUOTATION MARK
"\x93" => "\xE2\x80\x9C", # LEFT DOUBLE QUOTATION MARK
"\x94" => "\xE2\x80\x9D", # RIGHT DOUBLE QUOTATION MARK
"\x95" => "\xE2\x80\xA2", # BULLET
"\x96" => "\xE2\x80\x93", # EN DASH
"\x97" => "\xE2\x80\x94", # EM DASH
"\x98" => "\xCB\x9C", # SMALL TILDE
"\x99" => "\xE2\x84\xA2", # TRADE MARK SIGN
"\x9A" => "\xC5\xA1", # LATIN SMALL LETTER S WITH CARON
"\x9B" => "\xE2\x80\xBA", # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
"\x9C" => "\xC5\x93", # LATIN SMALL LIGATURE OE
"\x9E" => "\xC5\xBE", # LATIN SMALL LETTER Z WITH CARON
"\x9F" => "\xC5\xB8", # LATIN CAPITAL LETTER Y WITH DIAERESIS
);
for( $i=160; $i < 256; $i++ ) {
$ch = chr($i);
$byte_map[$ch] = iconv('ISO-8859-1', 'UTF-8', $ch);
}
}
define_byte_mappings();
function force_utf8( $input ) {
global $byte_map, $nibble_good_chars;
$output = '';
$char = '';
$rest = '';
while( $input != '' ) {
if ( preg_match( $nibble_good_chars, $input, $matches ) ) {
$output .= $matches[1];
$rest = $matches[2];
}
else {
preg_match( '/^(.)(.*)$/s', $input, $matches );
$char = $matches[1];
$rest = $matches[2];
if ( isset($byte_map[$char]) ) {
$output .= $byte_map[$char];
}
else {
# Must be valid UTF8 already
$output .= $char;
}
}
$input = $rest;
}
return $output;
}
}
/**
* Try and extract something like "Pacific/Auckland" or "America/Indiana/Indianapolis" if possible.
*/
function olson_from_tzstring( $tzstring ) {
global $c;
if ( function_exists('timezone_identifiers_list') && in_array($tzstring,timezone_identifiers_list()) ) return $tzstring;
if ( preg_match( '{((Antarctica|America|Africa|Atlantic|Asia|Australia|Indian|Europe|Pacific)/(([^/]+)/)?[^/]+)$}', $tzstring, $matches ) ) {
// dbg_error_log( 'INFO', 'Found timezone "%s" from string "%s"', $matches[1], $tzstring );
return $matches[1];
}
switch( $tzstring ) {
case 'New Zealand Standard Time': case 'New Zealand Daylight Time':
return 'Pacific/Auckland';
break;
case 'Central Standard Time': case 'Central Daylight Time': case 'US/Central':
return 'America/Chicago';
break;
case 'Eastern Standard Time': case 'Eastern Daylight Time': case 'US/Eastern':
case '(UTC-05:00) Eastern Time (US & Canada)':
return 'America/New_York';
break;
case 'Pacific Standard Time': case 'Pacific Daylight Time': case 'US/Pacific':
return 'America/Los_Angeles';
break;
case 'Mountain Standard Time': case 'Mountain Daylight Time': case 'US/Mountain': case 'Mountain Time':
return 'America/Denver';
// The US 'Mountain Time' can in fact be America/(Denver|Boise|Phoenix|Shiprock) which
// all vary to some extent due to differing DST rules.
break;
case '(GMT-07.00) Arizona':
return 'America/Phoenix';
break;
default:
if ( isset($c->timezone_translations) && is_array($c->timezone_translations)
&& !empty($c->timezone_translations[$tzstring]) )
return $c->timezone_translations[$tzstring];
}
return null;
}
if ( !function_exists("deprecated") ) {
function deprecated( $method ) {
global $c;
if ( isset($c->dbg['ALL']) || isset($c->dbg['deprecated']) ) {
$stack = debug_backtrace();
array_shift($stack);
if ( preg_match( '{/inc/iCalendar.php$}', $stack[0]['file'] ) && $stack[0]['line'] > __LINE__ ) return;
@error_log( sprintf( $c->sysabbr.':DEPRECATED: Call to deprecated method "%s"', $method));
foreach( $stack AS $k => $v ) {
@error_log( sprintf( $c->sysabbr.': ==> called from line %4d of %s', $v['line'], $v['file']));
}
}
}
}
if ( !function_exists("gzdecode") ) {
function gzdecode( $instring ) {
global $c;
if ( !isset($c->use_pipe_gunzip) || $c->use_pipe_gunzip ) {
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("file", "/dev/null", "a") // stderr is discarded
);
$process = proc_open('gunzip',$descriptorspec, $pipes);
if ( is_resource($process) ) {
fwrite($pipes[0],$instring);
fclose($pipes[0]);
$outstring = stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
return $outstring;
}
return '';
}
else {
$g=tempnam('./','gz');
file_put_contents($g,$instring);
ob_start();
readgzfile($g);
$d=ob_get_clean();
unlink($g);
return $d;
}
}
}
/**
* Return the AWL version
*/
function awl_version() {
global $c;
$c->awl_library_version = 0.54;
return $c->awl_library_version;
}

118
include/Translation.php Normal file
View File

@ -0,0 +1,118 @@
<?php
/**
* Functions involved in translating with gettext
* @package awl
* @subpackage Translation
* @author Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Catalyst IT Ltd
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
*/
if ( !function_exists('i18n') ) {
/**
* Mark a string as being internationalized. This is a semaphore method; it
* does nothing but it allows us to easily identify strings that require
* translation. Generally this is used to mark strings that will be stored
* in the database (like descriptions of permissions).
*
* AWL uses GNU gettext for internationalization (i18n) and localization (l10n) of
* text presented to the user. Gettext needs to know about all places involving strings,
* that must be translated. Mark any place, where localization at runtime shall take place
* by using the function translate().
*
* In the help I have used 'xlate' rather than 'translate' and 'x18n' rather than 'i18n'
* so that the tools skip this particular file for translation :-)
*
* E.g. instead of:
* print 'TEST to be displayed in different languages';
* use:
* print xlate('TEST to be displayed in different languages');
* and you are all set for pure literals. The translation teams will receive that literal
* string as a job to translate and will translate it (when the message is clear enough).
* At runtime the message is then localized when printed.
* The input string can contain a hint to assist translators:
* print xlate('TT <!-- abbreviation for Translation Test -->');
* The hint portion of the string will not be printed.
*
* But consider this case:
* $message_to_be_localized = 'TEST to be displayed in different languages';
* print xlate($message_to_be_localized);
*
* The translate() function is called in the right place for runtime handling, but there
* is no message at gettext preprocessing time to be given to the translation teams,
* just a variable name. Translation of the variable name would break the code! So all
* places potentially feeding this variable have to be marked to be given to translation
* teams, but not translated at runtime!
*
* This method resolves all such cases. Simply mark the candidates:
* $message_to_be_localized = x18n('TEST to be displayed in different languages');
* print xlate($message_to_be_localized);
*
* @param string the value
* @return string the same value
*/
function i18n($value) {
return $value; /* Just pass the value through */
}
}
if ( !function_exists('translate') ) {
/**
* Convert a string in English to whatever this user's locale is
*/
if ( function_exists('gettext') ) {
function translate( $en ) {
if ( ! isset($en) || $en == '' ) return $en;
$xl = gettext($en);
dbg_error_log('I18N','Translated =%s= into =%s=', $en, $xl );
return $xl;
}
}
else {
function translate( $en ) {
return $en;
}
}
}
if ( !function_exists('init_gettext') ) {
/**
* Initialise our use of Gettext
*/
function init_gettext( $domain, $location ) {
if ( !function_exists('bindtextdomain') ) return;
bindtextdomain( $domain, $location );
$codeset = bind_textdomain_codeset( $domain, 'UTF-8' );
textdomain( $domain );
dbg_error_log('I18N','Bound domain =%s= to location =%s= using character set =%s=', $domain, $location, $codeset );
}
}
if ( !function_exists('awl_set_locale') ) {
/**
* Set the translation to the user's locale. At this stage all we do is
* call the gettext function.
*/
function awl_set_locale( $locale ) {
global $c;
if ( !is_array($locale) && ! preg_match('/^[a-z]{2}(_[A-Z]{2})?\./', $locale ) ) {
$locale = array( $locale, $locale.'.UTF-8');
}
if ( !function_exists('setlocale') ) {
dbg_log_array('WARN','No "setlocale()" function? PHP gettext support missing?' );
return;
}
if ( $newlocale = setlocale( LC_ALL, $locale) ) {
dbg_error_log('I18N','Set locale to =%s=', $newlocale );
$c->current_locale = $newlocale;
}
else {
dbg_log_array('I18N','Unsupported locale: ', $locale, false );
}
}
}

332
include/XMLDocument.php Normal file
View File

@ -0,0 +1,332 @@
<?php
/**
* Handling of namespacing for XML documents
*
* @package awl
* @subpackage XMLDocument
* @author Andrew McMillan <andrew@morphoss.com>
* @copyright Morphoss Ltd - http://www.morphoss.com/
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later
*
*/
require_once("XMLElement.php");
/**
* A class for XML Documents which will contain namespaced XML elements
*
* @package awl
*/
class XMLDocument {
/**#@+
* @access private
*/
/**
* holds the namespaces which this document has been configured for.
* @var namespaces
*/
private $namespaces;
/**
* holds the prefixes which are shorthand for the namespaces.
* @var prefixes
*/
private $prefixes;
/**
* Holds the root document for the tree
* @var root
*/
private $root;
/**
* Simple XMLDocument constructor
*
* @param array $namespaces An array of 'namespace' => 'prefix' pairs, where the prefix is used as a short form for the namespace.
*/
function __construct( $namespaces = null ) {
$this->namespaces = array();
$this->prefixes = array();
if ( $namespaces != null ) {
foreach( $namespaces AS $ns => $prefix ) {
$this->namespaces[$ns] = $prefix;
$this->prefixes[$prefix] = $prefix;
}
}
$this->next_prefix = 0;
}
/**
* Add a new namespace to the document, optionally specifying it's short prefix
*
* @param string $namespace The full namespace name to be added
* @param string $prefix An optional short form for the namespace.
*/
function AddNamespace( $namespace, $prefix = null ) {
if ( !isset($this->namespaces[$namespace]) ) {
if ( isset($prefix) && ($prefix == "" || isset($this->prefixes[$prefix])) ) $prefix = null;
if ( $prefix == null ) {
// Try and build a prefix based on the first alphabetic character of the last element of the namespace
if ( preg_match('/^(.*):([^:]+)$/', $namespace, $matches) ) {
$alpha = preg_replace( '/[^a-z]/i', '', $matches[2] );
$prefix = strtoupper(substr($alpha,0,1));
}
else {
$prefix = 'X';
}
$i = "";
if ( isset($this->prefixes[$prefix]) ) {
for ( $i=1; $i<10 && isset($this->prefixes["$prefix$i"]); $i++ ) {
}
}
if ( isset($this->prefixes["$prefix$i"]) ) {
dbg_error_log("ERROR", "Cannot find a free prefix for this namespace");
exit;
}
$prefix = "$prefix$i";
dbg_error_log("XMLDocument", "auto-assigning prefix of '%s' for ns of '%s'", $prefix, $namespace );
}
else if ( $prefix == "" || isset($this->prefixes[$prefix]) ) {
dbg_error_log("ERROR", "Cannot assign the same prefix to two different namespaces");
exit;
}
$this->prefixes[$prefix] = $prefix;
$this->namespaces[$namespace] = $prefix;
}
else {
if ( isset($this->namespaces[$namespace]) && $this->namespaces[$namespace] != $prefix ) {
dbg_error_log("ERROR", "Cannot use the same namespace with two different prefixes");
exit;
}
$this->prefixes[$prefix] = $prefix;
$this->namespaces[$namespace] = $prefix;
}
}
/**
* Return the default namespace for this document
*/
function DefaultNamespace() {
foreach( $this->namespaces AS $k => $v ) {
if ( $v == '' ) {
return $k;
}
}
return '';
}
/**
* Return a tag with namespace stripped and replaced with a short form, and the ns added to the document.
*
*/
function GetXmlNsArray() {
$ns = array();
foreach( $this->namespaces AS $n => $p ) {
if ( $p == "" ) $ns["xmlns"] = $n; else $ns["xmlns:$p"] = $n;
}
return $ns;
}
/**
* Return a tag with namespace stripped and replaced with a short form, and the ns added to the document.
*
* @param string $in_tag The tag we want a namespace prefix on.
* @param string $namespace The namespace we want it in (which will be parsed from $in_tag if not present
* @param string $prefix The prefix we would like to use. Leave it out and one will be assigned.
*
* @return string The tag with a namespace prefix consistent with previous tags in this namespace.
*/
function Tag( $in_tag, $namespace=null, $prefix=null ) {
if ( $namespace == null ) {
// Attempt to split out from namespace:tag
if ( preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
$namespace = $matches[1];
$tag = $matches[2];
}
else {
// There is nothing we can do here
return $in_tag;
}
}
else {
$tag = $in_tag;
}
if ( !isset($this->namespaces[$namespace]) ) {
$this->AddNamespace( $namespace, $prefix );
}
$prefix = $this->namespaces[$namespace];
return $prefix . ($prefix == "" ? "" : ":") . $tag;
}
static public $ns_dav = 'DAV:';
static public $ns_caldav = 'urn:ietf:params:xml:ns:caldav';
static public $ns_carddav = 'urn:ietf:params:xml:ns:carddav';
static public $ns_calendarserver = 'http://calendarserver.org/ns/';
/**
* Special helper for namespaced tags.
*
* @param object $element The tag are adding a new namespaced element to
* @param string $tag the tag name, possibly prefixed with the namespace
* @param mixed $content The content of the tag
* @param array $attributes An array of key/value pairs of attributes.
* @param string $namespace The namespace for the tag
*
*/
function NSElement( &$element, $in_tag, $content=false, $attributes=false, $namespace=null ) {
if ( $namespace == null && preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
$namespace = $matches[1];
if ( preg_match('{^[A-Z][A-Z0-9]*$}', $namespace ) ) {
throw new Exception("Dodgy looking namespace from '".$in_tag."'!");
}
$tag = $matches[2];
}
else {
$tag = $in_tag;
if ( isset($namespace) ) {
$tag = str_replace($namespace.':', '', $tag);
}
}
if ( isset($namespace) && !isset($this->namespaces[$namespace]) ) $this->AddNamespace( $namespace );
return $element->NewElement( $tag, $content, $attributes, $namespace );
}
/**
* Special helper for tags in the DAV: namespace.
*
* @param object $element The tag are adding a new namespaced element to
* @param string $tag the tag name
* @param mixed $content The content of the tag
* @param array $attributes An array of key/value pairs of attributes.
*/
function DAVElement( &$element, $tag, $content=false, $attributes=false ) {
if ( !isset($this->namespaces[self::$ns_dav]) ) $this->AddNamespace( self::$ns_dav, '' );
return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_dav );
}
/**
* Special helper for tags in the urn:ietf:params:xml:ns:caldav namespace.
*
* @param object $element The tag are adding a new namespaced element to
* @param string $tag the tag name
* @param mixed $content The content of the tag
* @param array $attributes An array of key/value pairs of attributes.
*/
function CalDAVElement( &$element, $tag, $content=false, $attributes=false ) {
if ( !isset($this->namespaces[self::$ns_caldav]) ) $this->AddNamespace( self::$ns_caldav, 'C' );
return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_caldav );
}
/**
* Special helper for tags in the urn:ietf:params:xml:ns:carddav namespace.
*
* @param object $element The tag are adding a new namespaced element to
* @param string $tag the tag name
* @param mixed $content The content of the tag
* @param array $attributes An array of key/value pairs of attributes.
*/
function CardDAVElement( &$element, $tag, $content=false, $attributes=false ) {
if ( !isset($this->namespaces[self::$ns_carddav]) ) $this->AddNamespace( self::$ns_carddav, 'VC' );
return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_carddav );
}
/**
* Special helper for tags in the urn:ietf:params:xml:ns:caldav namespace.
*
* @param object $element The tag are adding a new namespaced element to
* @param string $tag the tag name
* @param mixed $content The content of the tag
* @param array $attributes An array of key/value pairs of attributes.
*/
function CalendarserverElement( &$element, $tag, $content=false, $attributes=false ) {
if ( !isset($this->namespaces[self::$ns_calendarserver]) ) $this->AddNamespace( self::$ns_calendarserver, 'A' );
return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_calendarserver );
}
/**
* @param string $in_tag The tag name of the new element, possibly namespaced
* @param mixed $content Either a string of content, or an array of sub-elements
* @param array $attributes An array of attribute name/value pairs
* @param array $xmlns An XML namespace specifier
*/
function NewXMLElement( $in_tag, $content=false, $attributes=false, $xmlns=null ) {
if ( $xmlns == null && preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
$xmlns = $matches[1];
$tagname = $matches[2];
}
else {
$tagname = $in_tag;
}
if ( isset($xmlns) && !isset($this->namespaces[$xmlns]) ) $this->AddNamespace( $xmlns );
return new XMLElement($tagname, $content, $attributes, $xmlns );
}
/**
* Render the document tree into (nicely formatted) XML
*
* @param mixed $root A root XMLElement or a tagname to create one with the remaining parameters.
* @param mixed $content Either a string of content, or an array of sub-elements
* @param array $attributes An array of attribute name/value pairs
* @param array $xmlns An XML namespace specifier
*
* @return A rendered namespaced XML document.
*/
function Render( $root, $content=false, $attributes=false, $xmlns=null ) {
if ( is_object($root) ) {
/** They handed us a pre-existing object. We'll just use it... */
$this->root = $root;
}
else {
/** We got a tag name, so we need to create the root element */
$this->root = $this->NewXMLElement( $root, $content, $attributes, $xmlns );
}
/**
* Add our namespace attributes here.
*/
foreach( $this->namespaces AS $n => $p ) {
$this->root->SetAttribute( 'xmlns'.($p == '' ? '' : ':') . $p, $n);
}
/** And render... */
return $this->root->Render(0,'<?xml version="1.0" encoding="utf-8" ?>');
}
/**
* Return a DAV::href XML element, or an array of them
* @param mixed $url The URL (or array of URLs) to be wrapped in DAV::href tags
*
* @return XMLElement The newly created XMLElement object.
*/
function href($url) {
if ( is_array($url) ) {
$set = array();
foreach( $url AS $href ) {
$set[] = $this->href( $href );
}
return $set;
}
if ( preg_match('[@+ ]',$url) ) {
trace_bug('URL "%s" was not encoded before call to XMLDocument::href()', $url );
$url = str_replace( '%2F', '/', rawurlencode($url));
}
return $this->NewXMLElement('href', $url, false, 'DAV:');
}
}

383
include/XMLElement.php Normal file
View File

@ -0,0 +1,383 @@
<?php
/**
* A class to assist with construction of XML documents
*
* @package awl
* @subpackage XMLElement
* @author Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later
*/
require_once('AWLUtilities.php');
/**
* A class for XML elements which may have attributes, or contain
* other XML sub-elements
*
* @package awl
*/
class XMLElement {
protected $tagname;
protected $xmlns;
protected $attributes;
protected $content;
protected $_parent;
/**
* Constructor - nothing fancy as yet.
*
* @param string $tagname The tag name of the new element
* @param mixed $content Either a string of content, or an array of sub-elements
* @param array $attributes An array of attribute name/value pairs
* @param string $xmlns An XML namespace specifier
*/
function __construct( $tagname, $content=false, $attributes=false, $xmlns=null ) {
$this->tagname=$tagname;
if ( gettype($content) == "object" ) {
// Subtree to be parented here
$this->content = array(&$content);
}
else {
// Array or text
$this->content = $content;
}
$this->attributes = $attributes;
if ( isset($xmlns) ) {
$this->xmlns = $xmlns;
}
else {
if ( preg_match( '{^(.*):([^:]*)$}', $tagname, $matches) ) {
$prefix = $matches[1];
$tag = $matches[2];
if ( isset($this->attributes['xmlns:'.$prefix]) ) {
$this->xmlns = $this->attributes['xmlns:'.$prefix];
}
}
else if ( isset($this->attributes['xmlns']) ) {
$this->xmlns = $this->attributes['xmlns'];
}
}
}
/**
* Count the number of elements
* @return int The number of elements
*/
function CountElements( ) {
if ( $this->content === false ) return 0;
if ( is_array($this->content) ) return count($this->content);
if ( $this->content == '' ) return 0;
return 1;
}
/**
* Set an element attribute to a value
*
* @param string The attribute name
* @param string The attribute value
*/
function SetAttribute($k,$v) {
if ( gettype($this->attributes) != "array" ) $this->attributes = array();
$this->attributes[$k] = $v;
if ( strtolower($k) == 'xmlns' ) {
$this->xmlns = $v;
}
}
/**
* Set the whole content to a value
*
* @param mixed The element content, which may be text, or an array of sub-elements
*/
function SetContent($v) {
$this->content = $v;
}
/**
* Accessor for the tag name
*
* @return string The tag name of the element
*/
function GetTag() {
return $this->tagname;
}
/**
* Accessor for the full-namespaced tag name
*
* @return string The tag name of the element, prefixed by the namespace
*/
function GetNSTag() {
return (empty($this->xmlns) ? '' : $this->xmlns . ':') . $this->tagname;
}
/**
* Accessor for a single attribute
* @param string $attr The name of the attribute.
* @return string The value of that attribute of the element
*/
function GetAttribute( $attr ) {
if ( $attr == 'xmlns' ) return $this->xmlns;
if ( isset($this->attributes[$attr]) ) return $this->attributes[$attr];
return null;
}
/**
* Accessor for the attributes
*
* @return array The attributes of this element
*/
function GetAttributes() {
return $this->attributes;
}
/**
* Accessor for the content
*
* @return array The content of this element
*/
function GetContent() {
return $this->content;
}
/**
* Return an array of elements matching the specified tag, or all elements if no tag is supplied.
* Unlike GetContent() this will always return an array.
*
* @return array The XMLElements within the tree which match this tag
*/
function GetElements( $tag=null, $recursive=false ) {
$elements = array();
if ( gettype($this->content) == "array" ) {
foreach( $this->content AS $k => $v ) {
if ( empty($tag) || $v->GetNSTag() == $tag ) {
$elements[] = $v;
}
if ( $recursive ) {
$elements = $elements + $v->GetElements($tag,true);
}
}
}
else if ( empty($tag) || (isset($v->content->tagname) && $v->content->GetNSTag() == $tag) ) {
$elements[] = $this->content;
}
return $elements;
}
/**
* Return an array of elements matching the specified path
*
* @return array The XMLElements within the tree which match this tag
*/
function GetPath( $path ) {
$elements = array();
// printf( "Querying within '%s' for path '%s'\n", $this->tagname, $path );
if ( !preg_match( '#(/)?([^/]+)(/?.*)$#', $path, $matches ) ) return $elements;
// printf( "Matches: %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3] );
if ( $matches[2] == '*' || $matches[2] == $this->GetNSTag()) {
if ( $matches[3] == '' ) {
/**
* That is the full path
*/
$elements[] = $this;
}
else if ( gettype($this->content) == "array" ) {
/**
* There is more to the path, so we recurse into that sub-part
*/
foreach( $this->content AS $k => $v ) {
$elements = array_merge( $elements, $v->GetPath($matches[3]) );
}
}
}
if ( $matches[1] != '/' && gettype($this->content) == "array" ) {
/**
* If our input $path was not rooted, we recurse further
*/
foreach( $this->content AS $k => $v ) {
$elements = array_merge( $elements, $v->GetPath($path) );
}
}
// printf( "Found %d within '%s' for path '%s'\n", count($elements), $this->tagname, $path );
return $elements;
}
/**
* Add a sub-element
*
* @param object An XMLElement to be appended to the array of sub-elements
*/
function AddSubTag(&$v) {
if ( gettype($this->content) != "array" ) $this->content = array();
$this->content[] =& $v;
return count($this->content);
}
/**
* Add a new sub-element
*
* @param string The tag name of the new element
* @param mixed Either a string of content, or an array of sub-elements
* @param array An array of attribute name/value pairs
*
* @return objectref A reference to the new XMLElement
*/
function &NewElement( $tagname, $content=false, $attributes=false, $xmlns=null ) {
if ( gettype($this->content) != "array" ) $this->content = array();
$element = new XMLElement($tagname,$content,$attributes,$xmlns);
$this->content[] =& $element;
return $element;
}
/**
* Render just the internal content
*
* @return string The content of this element, as a string without this element wrapping it.
*/
function RenderContent($indent=0, $nslist=null, $force_xmlns=false ) {
$r = "";
if ( is_array($this->content) ) {
/**
* Render the sub-elements with a deeper indent level
*/
$r .= "\n";
foreach( $this->content AS $k => $v ) {
if ( is_object($v) ) {
$r .= $v->Render($indent+1, "", $nslist, $force_xmlns);
}
}
$r .= substr(" ",0,$indent);
}
else {
/**
* Render the content, with special characters escaped
*
*/
if(strpos($this->content, '<![CDATA[')===0 && strrpos($this->content, ']]>')===strlen($this->content)-3)
$r .= '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', substr($this->content, 9, -3)) . ']]>';
else if ( defined('ENT_XML1') && defined('ENT_DISALLOWED') )
// Newer PHP versions allow specifying ENT_XML1, but default to ENT_HTML401. Go figure. #PHPWTF
$r .= htmlspecialchars($this->content, ENT_NOQUOTES | ENT_XML1 | ENT_DISALLOWED );
// Need to work out exactly how to do this in PHP.
// else if ( preg_match('{^[\t\n\r\x0020-\xD7FF\xE000-\xFFFD\x10000-\x10FFFF]+$}u', utf8ToUnicode($this->content)) )
// $r .= '<![CDATA[' . $this->content . ']]>';
else
// Older PHP versions default to ENT_XML1.
$r .= htmlspecialchars($this->content, ENT_NOQUOTES );
}
return $r;
}
/**
* Render the document tree into (nicely formatted) XML
*
* @param int The indenting level for the pretty formatting of the element
*/
function Render($indent=0, $xmldef="", $nslist=null, $force_xmlns=false) {
$r = ( $xmldef == "" ? "" : $xmldef."\n");
$attr = "";
$tagname = $this->tagname;
$xmlns_done = false;
if ( gettype($this->attributes) == "array" ) {
/**
* Render the element attribute values
*/
foreach( $this->attributes AS $k => $v ) {
if ( preg_match('#^xmlns(:?(.+))?$#', $k, $matches ) ) {
// if ( $force_xmlns ) printf( "1: %s: %s\n", $this->tagname, $this->xmlns );
if ( !isset($nslist) ) $nslist = array();
$prefix = (isset($matches[2]) ? $matches[2] : '');
if ( isset($nslist[$v]) && $nslist[$v] == $prefix ) continue; // No need to include in list as it's in a wrapping element
$nslist[$v] = $prefix;
if ( !isset($this->xmlns) ) $this->xmlns = $v;
$xmlns_done = true;
}
$attr .= sprintf( ' %s="%s"', $k, htmlspecialchars($v) );
}
}
if ( isset($this->xmlns) && isset($nslist[$this->xmlns]) && $nslist[$this->xmlns] != '' ) {
// if ( $force_xmlns ) printf( "2: %s: %s\n", $this->tagname, $this->xmlns );
$tagname = $nslist[$this->xmlns] . ':' . $tagname;
if ( $force_xmlns ) $attr .= sprintf( ' xmlns="%s"', $this->xmlns);
}
else if ( isset($this->xmlns) && !isset($nslist[$this->xmlns]) && gettype($this->attributes) == 'array' && !isset($this->attributes[$this->xmlns]) ) {
// if ( $force_xmlns ) printf( "3: %s: %s\n", $this->tagname, $this->xmlns );
$attr .= sprintf( ' xmlns="%s"', $this->xmlns);
}
else if ( $force_xmlns && isset($this->xmlns) && ! $xmlns_done ) {
// printf( "4: %s: %s\n", $this->tagname, $this->xmlns );
$attr .= sprintf( ' xmlns="%s"', $this->xmlns);
}
$r .= substr(" ",0,$indent) . '<' . $tagname . $attr;
if ( (is_array($this->content) && count($this->content) > 0) || (!is_array($this->content) && strlen($this->content) > 0) ) {
$r .= ">";
$r .= $this->RenderContent($indent,$nslist,$force_xmlns);
$r .= '</' . $tagname.">\n";
}
else {
$r .= "/>\n";
}
return $r;
}
function __tostring() {
return $this->Render();
}
}
/**
* Rebuild an XML tree in our own style from the parsed XML tags using
* a tail-recursive approach.
*
* @param array $xmltags An array of XML tags we get from using the PHP XML parser
* @param intref &$start_from A pointer to our current integer offset into $xmltags
* @return mixed Either a single XMLElement, or an array of XMLElement objects.
*/
function BuildXMLTree( $xmltags, &$start_from ) {
$content = array();
if ( !isset($start_from) ) $start_from = 0;
for( $i=0; $i < 50000 && isset($xmltags[$start_from]); $i++) {
$tagdata = $xmltags[$start_from++];
if ( !isset($tagdata) || !isset($tagdata['tag']) || !isset($tagdata['type']) ) break;
if ( $tagdata['type'] == "close" ) break;
$xmlns = null;
$tag = $tagdata['tag'];
if ( preg_match( '{^(.*):([^:]*)$}', $tag, $matches) ) {
$xmlns = $matches[1];
$tag = $matches[2];
}
$attributes = ( isset($tagdata['attributes']) ? $tagdata['attributes'] : false );
if ( $tagdata['type'] == "open" ) {
$subtree = BuildXMLTree( $xmltags, $start_from );
$content[] = new XMLElement($tag, $subtree, $attributes, $xmlns );
}
else if ( $tagdata['type'] == "complete" ) {
$value = ( isset($tagdata['value']) ? $tagdata['value'] : false );
$content[] = new XMLElement($tag, $value, $attributes, $xmlns );
}
}
/**
* If there is only one element, return it directly, otherwise return the
* array of them
*/
if ( count($content) == 1 ) {
return $content[0];
}
return $content;
}

124
send_nextcloud.php Normal file
View File

@ -0,0 +1,124 @@
<?php
require_once('../SimpleCalDAVClient.php');
$firstNewEvent = 'BEGIN:VCALENDAR
PRODID:-//Atelier Lalis//FR
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:[date_creation]
LAST-MODIFIED:[date_creation]
DTSTAMP:[date_creation]
UID:Atelier[UID]
CATEGORIES:Lalis, ateliers
CLASS:PUBLIC
SUMMARY:Atelier du [date]
DTSTART;TZID=Europe/Paris:[datetime_start]
DTEND;TZID=Europe/Paris:[datetime_stop]
LOCATION:Lalis 7 place Louis Chazette 69001 Lyon
DESCRIPTION:Atelier Lalis\nInscription: [url]
END:VEVENT
END:VCALENDAR';
$client = new SimpleCalDAVClient();
try {
/*
* To establish a connection and to choose a calendar on the server, use
* connect()
* findCalendars()
* setCalendar()
*/
$client->connect('https://https://lalis69.ddns.net:10443/laliscloud/remote.php/dav/principals/users/DTux/', 'lalis', 'Lalis69_cloud');
$arrayOfCalendars = $client->findCalendars(); // Returns an array of all accessible calendars on the server.
$client->setCalendar($arrayOfCalendars["myCalendarID"]); // Here: Use the calendar ID of your choice. If you don't know which calendar ID to use, try config/listCalendars.php
/*
* You can create calendar objects (e.g. events, todos,...) on the server with create().
* Just pass a string with the iCalendar-data which should be saved on the server.
* The function returns a CalDAVObject (see CalDAVObject.php) with the stored information about the new object on the server
*/
$firstNewEventOnServer = $client->create($firstNewEvent); // Creates $firstNewEvent on the server and a CalDAVObject representing the event.
$secondNewEventOnServer = $client->create($secondNewEvent); // Creates $firstNewEvent on the server and a CalDAVObject representing the event.
/*
* You can getEvents with getEvents()
*/
$client->getEvents('20140418T103000Z', '20140419T200000Z'); // Returns array($firstNewEventOnServer, $secondNewEventOnServer);
/*
* An CalDAVObject $o has three attributes
* $o->getHref(): Link to the object on the server
* $o->getData(): The iCalendar-data describing the object
* $o->getEtag(): see CalDAVObject.php
*
* $o->getHref() and $o->getEtag() can be used to change or to delete the object.
* $o->getData() can be processed further on, e.g. printed
*/
$firstNewEventOnServer = $client->change($firstNewEventOnServer->getHref(),$changedFirstEvent, $firstNewEventOnServer->getEtag());
// Change the first event on the server from $firstNewEvent to $changedFirstEvent
// and overwrite $firstNewEventOnServer with the new representation of the changed event on the server.
$events = $client->getEvents('20140418T103000Z', '20140419T200000Z'); // Returns array($secondNewEventOnServer);
echo $events[0]->getData(); // Prints $secondNewEvent. See CalDAVObject.php
$client->delete($secondNewEventOnServer->getHref(), $secondNewEventOnServer->getEtag()); // Deletes the second new event from the server.
$client->getEvents('20140418T103000Z', '20140419T200000Z'); // Returns an empty array
/*
* You can create custom queries to the server via CalDAVFilter. See CalDAVFilter.php
*/
$filter = new CalDAVFilter("VEVENT");
$filter->mustInclude("SUMMARY"); // Should include a SUMMARY
$filter->mustInclude("PRIORITY", TRUE); // Should not include a PRIORITY
$filter->mustIncludeMatchSubstr("DESCRIPTION", "ExampleDescription2", TRUE); // "ExampleDescription1" should not be a substring of the DESCRIPTION
$filter->mustOverlapWithTimerange(NULL, "20140420T100000Z");
$events = $client->getCustomReport($filter->toXML()); // Returns array($changedFirstEvent)
$client->delete($events[0]->getHref(), $events[0]->getEtag()); // Deletes the changed first event from the server.
}
catch (Exception $e) {
echo $e->__toString();
}
?>