diff options
author | Georg Ehrke <dev@georgswebsite.de> | 2012-04-24 21:59:56 +0200 |
---|---|---|
committer | Georg Ehrke <dev@georgswebsite.de> | 2012-04-24 21:59:56 +0200 |
commit | f17eea506afaab0c0755d0fefe3a3f62eeb38e9d (patch) | |
tree | b5c1f8a33aef46f1a718d5313bef8f4e52700acf /apps | |
parent | d6346b5b0bece4feecc36b7b97308dd3a5a4d6cc (diff) | |
parent | 9b134b063683aca678e19912ebc3928321751714 (diff) | |
download | nextcloud-server-f17eea506afaab0c0755d0fefe3a3f62eeb38e9d.tar.gz nextcloud-server-f17eea506afaab0c0755d0fefe3a3f62eeb38e9d.zip |
fix merge conflicts
Diffstat (limited to 'apps')
31 files changed, 585 insertions, 1242 deletions
diff --git a/apps/contacts/ajax/importaddressbook.php b/apps/contacts/ajax/importaddressbook.php new file mode 100644 index 00000000000..5776c801a76 --- /dev/null +++ b/apps/contacts/ajax/importaddressbook.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_Util::checkAppEnabled('contacts'); +$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); +$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); +$maxUploadFilesize = min($upload_max_filesize, $post_max_size); + +$freeSpace=OC_Filesystem::free_space('/'); +$freeSpace=max($freeSpace,0); +$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace); + +$tmpl = new OC_Template('contacts', 'part.importaddressbook'); +$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); +$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); +$tmpl->printpage(); +?> diff --git a/apps/contacts/ajax/uploadimport.php b/apps/contacts/ajax/uploadimport.php new file mode 100644 index 00000000000..ab680c8823f --- /dev/null +++ b/apps/contacts/ajax/uploadimport.php @@ -0,0 +1,50 @@ +<?php +/** + * ownCloud - Addressbook + * + * @author Thomas Tanghus + * @copyright 2012 Thomas Tanghus <thomas@tanghus.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +// Init owncloud +require_once('../../../lib/base.php'); + +// Check if we are a user +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/uploadimport.php: '.$msg, OC_Log::ERROR); + exit(); +} +function debug($msg) { + OC_Log::write('contacts','ajax/uploadimport.php: '.$msg, OC_Log::DEBUG); +} + +// If it is a Drag'n'Drop transfer it's handled here. +$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false); +if($fn) { + $view = OC_App::getStorage('contacts'); + $tmpfile = md5(rand()); + if($view->file_put_contents('/'.$tmpfile, file_get_contents('php://input'))) { + debug($fn.' uploaded'); + OC_JSON::success(array('data' => array('path'=>'', 'file'=>$tmpfile))); + } else { + bailOut(OC_Contacts_App::$l10n->t('Error uploading contacts to storage.')); + } +} + +?> diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 15f98cffbfb..2ea99a56a7b 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -33,8 +33,9 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } #address.form dt { min-width: 5em; } #address.form dl { min-width: 10em; } - .loading {/*cursor: progress; */ cursor: wait; } +.droptarget { margin: 0.5em; padding: 0.5em; border: thin solid #ccc; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; } +.droppable { margin: 0.5em; padding: 0.5em; border: thin dashed #333; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; } .float { float: left; } .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe, .upload, .cloud { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; opacity: 0.1; } @@ -51,6 +52,7 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } #identityprops { /*position: absolute; top: 2.5em; left: 0px;*/ } /*#contact_photo { max-width: 250px; }*/ #contact_identity { min-width: 30em; } +#note { min-width: 200px; } .contactsection { position: relative; float: left; /*max-width: 40em;*/ padding: 0.5em; height: auto; border: thin solid lightgray;/* -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8;*/ } .contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } @@ -90,5 +92,4 @@ input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; } .propertylist li > input[type="checkbox"],input[type="radio"] { float: left; clear: left; width: 20px; height: 20px; vertical-align: middle; } .propertylist li > select { float: left; max-width: 8em; } .typelist { float: left; max-width: 10em; } /* for multiselect */ -.addresslist { clear: both; } - +.addresslist { clear: both; }
\ No newline at end of file diff --git a/apps/contacts/import.php b/apps/contacts/import.php index b80021e00ea..ca2c1e1605d 100644 --- a/apps/contacts/import.php +++ b/apps/contacts/import.php @@ -17,8 +17,14 @@ if(is_writable('import_tmp/')){ fwrite($progressfopen, '10'); fclose($progressfopen); } -$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']); -if($_POST['method'] == 'new'){ +$view = $file = null; +if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { + $view = OC_App::getStorage('contacts'); + $file = $view->file_get_contents('/' . $_POST['file']); +} else { + $file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']); +} +if(isset($_POST['method']) && $_POST['method'] == 'new'){ $id = OC_Contacts_Addressbook::add(OC_User::getUser(), $_POST['addressbookname']); OC_Contacts_Addressbook::setActive($id, 1); }else{ @@ -99,12 +105,16 @@ if(is_writable('import_tmp/')){ if(count($parts) == 1){ $importready = array($file); } +$imported = 0; +$failed = 0; foreach($importready as $import){ $card = OC_VObject::parse($import); if (!$card) { + $failed += 1; OC_Log::write('contacts','Import: skipping card. Error parsing VCard: '.$import, OC_Log::ERROR); continue; // Ditch cards that can't be parsed by Sabre. } + $imported += 1; OC_Contacts_VCard::add($id, $card); } //done the import @@ -117,4 +127,9 @@ sleep(3); if(is_writable('import_tmp/')){ unlink($progressfile); } -OC_JSON::success(); +if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { + if(!$view->unlink('/' . $_POST['file'])) { + OC_Log::write('contacts','Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'], OC_Log::ERROR); + } +} +OC_JSON::success(array('data' => array('imported'=>$imported, 'failed'=>$failed))); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index f684576b787..b94bf77c5ec 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -1263,7 +1263,8 @@ Contacts={ for(ptype in this.data.TEL[phone]['parameters'][param]) { var pt = this.data.TEL[phone]['parameters'][param][ptype]; $('#phonelist li:last-child').find('select option').each(function(){ - if ($(this).val().toUpperCase() == pt.toUpperCase()) { + //if ($(this).val().toUpperCase() == pt.toUpperCase()) { + if ($.inArray($(this).val().toUpperCase(), pt.toUpperCase().split(',')) > -1) { $(this).attr('selected', 'selected'); } }); @@ -1285,6 +1286,7 @@ Contacts={ }, }, Addressbooks:{ + droptarget:undefined, overview:function(){ if($('#chooseaddressbook_dialog').dialog('isOpen') == true){ $('#chooseaddressbook_dialog').dialog('moveToTop'); @@ -1317,14 +1319,13 @@ Contacts={ var tr = $(document.createElement('tr')) .load(OC.filePath('contacts', 'ajax', 'addbook.php')); $(object).closest('tr').after(tr).hide(); - /* TODO: Shouldn't there be some kinda error checking here? */ }, editAddressbook:function(object, bookid){ var tr = $(document.createElement('tr')) .load(OC.filePath('contacts', 'ajax', 'editaddressbook.php') + "?bookid="+bookid); $(object).closest('tr').after(tr).hide(); }, - deleteAddressbook:function(bookid){ + deleteAddressbook:function(obj, bookid){ var check = confirm("Do you really want to delete this address book?"); if(check == false){ return false; @@ -1332,9 +1333,10 @@ Contacts={ $.post(OC.filePath('contacts', 'ajax', 'deletebook.php'), { id: bookid}, function(jsondata) { if (jsondata.status == 'success'){ - $('#chooseaddressbook_dialog').dialog('destroy').remove(); + $(obj).closest('tr').remove(); + //$('#chooseaddressbook_dialog').dialog('destroy').remove(); Contacts.UI.Contacts.update(); - Contacts.UI.Addressbooks.overview(); + //Contacts.UI.Addressbooks.overview(); } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert('Error: ' + data.message); @@ -1342,8 +1344,95 @@ Contacts={ }); } }, - doImport:function(){ - Contacts.UI.notImplemented(); + loadImportHandlers:function() { + this.droptarget = $('#import_drop_target'); + console.log($('#import_drop_target').html()); + $(this.droptarget).bind('dragover',function(event){ + $(event.target).addClass('droppable'); + event.stopPropagation(); + event.preventDefault(); + }); + $(this.droptarget).bind('dragleave',function(event){ + $(event.target).removeClass('droppable'); + }); + $(this.droptarget).bind('drop',function(event){ + event.stopPropagation(); + event.preventDefault(); + console.log('drop'); + $(event.target).removeClass('droppable'); + $(event.target).html(t('contacts', 'Uploading...')); + Contacts.UI.loading(event.target, true); + $.fileUpload(event.originalEvent.dataTransfer.files); + }); + + $.fileUpload = function(files){ + console.log(files + ', ' + files.length); + var file = files[0]; + console.log('size: '+file.size+', type: '+file.type); + if(file.size > $('#max_upload').val()){ + OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large')); + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); + return; + } + if(file.type.indexOf('text') != 0) { + OC.dialogs.alert(t('contacts','You have dropped a file type that cannot be imported: ') + file.type, t('contacts','Wrong file type')); + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); + return; + } + var xhr = new XMLHttpRequest(); + + if (!xhr.upload) { + OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please upload the contacts file to ownCloud and import that way.'), t('contacts', 'Error')) + } + fileUpload = xhr.upload, + xhr.onreadystatechange = function() { + if (xhr.readyState == 4){ + response = $.parseJSON(xhr.responseText); + if(response.status == 'success') { + if(xhr.status == 200) { + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Importing...')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, true); + Contacts.UI.Addressbooks.doImport(response.data.path, response.data.file); + } else { + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); + OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error')); + } + } else { + OC.dialogs.alert(response.data.message, t('contacts', 'Error')); + } + } + }; + xhr.open("POST", 'ajax/uploadimport.php?file='+encodeURIComponent(file.name), true); + xhr.setRequestHeader('Cache-Control', 'no-cache'); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name)); + xhr.setRequestHeader('X-File-Size', file.size); + xhr.setRequestHeader('Content-Type', file.type); + xhr.send(file); + } + }, + importAddressbook:function(object){ + var tr = $(document.createElement('tr')) + .load(OC.filePath('contacts', 'ajax', 'importaddressbook.php')); + $(object).closest('tr').after(tr).hide(); + }, + doImport:function(path, file){ + var id = $('#importaddressbook_dialog').find('#book').val(); + console.log('Selected book: ' + id); + $.post(OC.filePath('contacts', '', 'import.php'), { id: id, path: path, file: file, fstype: 'OC_FilesystemView' }, + function(jsondata){ + if(jsondata.status == 'success'){ + Contacts.UI.Addressbooks.droptarget.html(t('contacts', 'Import done. Success/Failure: ')+jsondata.data.imported+'/'+jsondata.data.failed); + $('#chooseaddressbook_dialog').find('#close_button').val(t('contacts', 'OK')); + Contacts.UI.Contacts.update(); + } else { + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); }, submit:function(button, bookid){ var displayname = $("#displayname_"+bookid).val().trim(); @@ -1368,7 +1457,7 @@ Contacts={ } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } - }); + }); }, cancel:function(button, bookid){ $(button).closest('tr').prev().show().next().remove(); @@ -1385,7 +1474,6 @@ Contacts={ } else{ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); - //alert(jsondata.data.message); } }); setTimeout(Contacts.UI.Contacts.lazyupdate, 500); @@ -1507,13 +1595,13 @@ $(document).ready(function(){ }); $('#contacts_details_photo_wrapper').bind('dragover',function(event){ console.log('dragover'); - $(event.target).css('background-color','red'); + $(event.target).addClass('droppable'); event.stopPropagation(); event.preventDefault(); }); $('#contacts_details_photo_wrapper').bind('dragleave',function(event){ console.log('dragleave'); - $(event.target).css('background-color','white'); + $(event.target).removeClass('droppable'); //event.stopPropagation(); //event.preventDefault(); }); @@ -1521,7 +1609,7 @@ $(document).ready(function(){ event.stopPropagation(); event.preventDefault(); console.log('drop'); - $(event.target).css('background-color','white') + $(event.target).removeClass('droppable'); $.fileUpload(event.originalEvent.dataTransfer.files); }); @@ -1618,4 +1706,4 @@ $(document).ready(function() { $('.ui-autocomplete-loading').css('background', 'url('+OC.filePath('core', 'img', 'loading.gif')+' right center no-repeat'); $('#contacts_details_photo').css('background', 'url('+OC.filePath('core', 'img', 'loading.gif')+' no-repeat center center'); $('#contacts_propertymenu_button').css('background', 'url('+OC.filePath('core', 'img/actions', 'add.svg')+') no-repeat center'); -});
\ No newline at end of file +}); diff --git a/apps/contacts/lib/app.php b/apps/contacts/lib/app.php index 475d2c8dc2e..2c2cc331ed7 100644 --- a/apps/contacts/lib/app.php +++ b/apps/contacts/lib/app.php @@ -149,6 +149,7 @@ class OC_Contacts_App { 'WORK' => $l->t('Work'), 'TEXT' => $l->t('Text'), 'VOICE' => $l->t('Voice'), + 'MSG' => $l->t('Message'), 'FAX' => $l->t('Fax'), 'VIDEO' => $l->t('Video'), 'PAGER' => $l->t('Pager'), diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php index 90b037f76ee..96fc8cf7121 100644 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -141,10 +141,38 @@ class OC_Contacts_VCard{ } /** - * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) + * @brief Checks if a contact with the same UID already exist in the address book. + * @param $aid Address book ID. + * @param $uid UID (passed by reference). + * @returns true if the UID has been changed. + */ + protected static function trueUID($aid, &$uid) { + $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' ); + $uri = $uid.'.vcf'; + $result = $stmt->execute(array($aid,$uri)); + if($result->numRows() > 0){ + while(true) { + $tmpuid = substr(md5(rand().time()),0,10); + $uri = $tmpuid.'.vcf'; + $result = $stmt->execute(array($aid,$uri)); + if($result->numRows() > 0){ + continue; + } else { + $uid = $tmpuid; + return true; + } + } + } else { + return false; + } + } + + /** + * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing. + * @param aid Address book id. * @param vcard An OC_VObject of type VCARD (passed by reference). */ - protected static function updateValuesFromAdd(&$vcard) { // any suggestions for a better method name? ;-) + protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-) $stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE'); $typeprops = array('ADR', 'TEL', 'EMAIL'); $upgrade = false; @@ -207,14 +235,19 @@ class OC_Contacts_VCard{ } if(!$uid) { $vcard->setUID(); + $uid = $vcard->getAsString('UID'); OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid,OC_Log::DEBUG); } + if(self::trueUID($aid, $uid)) { + $vcard->setString('UID', $uid); + } $vcard->setString('VERSION','3.0'); // Add product ID is missing. $prodid = trim($vcard->getAsString('PRODID')); if(!$prodid) { $appinfo = OC_App::getAppInfo('contacts'); - $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appinfo['version'].'//EN'; + $appversion = OC_App::getAppVersion('contacts'); + $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN'; $vcard->setString('PRODID', $prodid); } $now = new DateTime; @@ -236,7 +269,7 @@ class OC_Contacts_VCard{ OC_Contacts_App::loadCategoriesFromVCard($card); - self::updateValuesFromAdd($card); + self::updateValuesFromAdd($aid, $card); $fn = $card->getAsString('FN'); if (empty($fn)) { diff --git a/apps/contacts/templates/part.chooseaddressbook.php b/apps/contacts/templates/part.chooseaddressbook.php index 90894220ef8..adfc8c15161 100644 --- a/apps/contacts/templates/part.chooseaddressbook.php +++ b/apps/contacts/templates/part.chooseaddressbook.php @@ -1,4 +1,4 @@ -<div id="chooseaddressbook_dialog" title="<?php echo $l->t("Choose active Address Books"); ?>"> +<div id="chooseaddressbook_dialog" title="<?php echo $l->t("Configure Address Books"); ?>"> <table width="100%" style="border: 0;"> <?php $option_addressbooks = OC_Contacts_Addressbook::all(OC_User::getUser()); @@ -14,6 +14,7 @@ for($i = 0; $i < count($option_addressbooks); $i++){ <tr> <td colspan="5" style="padding: 0.5em;"> <a class="button" href="#" onclick="Contacts.UI.Addressbooks.newAddressbook(this);"><?php echo $l->t('New Address Book') ?></a> + <a class="button" href="#" onclick="Contacts.UI.Addressbooks.importAddressbook(this);"><?php echo $l->t('Import from VCF') ?></a> </td> </tr> <tr> diff --git a/apps/contacts/templates/part.chooseaddressbook.rowfields.php b/apps/contacts/templates/part.chooseaddressbook.rowfields.php index 8518c1acd13..20b67a4161e 100644 --- a/apps/contacts/templates/part.chooseaddressbook.rowfields.php +++ b/apps/contacts/templates/part.chooseaddressbook.rowfields.php @@ -2,4 +2,4 @@ // FIXME: Make this readable. echo "<td width=\"20px\"><input id=\"active_" . $_['addressbook']["id"] . "\" type=\"checkbox\" onClick=\"Contacts.UI.Addressbooks.activation(this, " . $_['addressbook']["id"] . ")\"" . (OC_Contacts_Addressbook::isActive($_['addressbook']["id"]) ? ' checked="checked"' : '') . "></td>"; echo "<td><label for=\"active_" . $_['addressbook']["id"] . "\">" . htmlspecialchars($_['addressbook']["displayname"]) . "</label></td>"; - echo "<td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.showCardDAVUrl('" . OC_User::getUser() . "', '" . rawurlencode($_['addressbook']["uri"]) . "');\" title=\"" . $l->t("CardDav Link") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/public.svg\"></a></td><td width=\"20px\"><a href=\"export.php?bookid=" . $_['addressbook']["id"] . "\" title=\"" . $l->t("Download") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/download.svg\"></a></td><td width=\"20px\"><a href=\"#\" title=\"" . $l->t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\"><img class=\"svg action\" src=\"../../core/img/actions/rename.svg\"></a></td><td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.Addressbooks.deleteAddressbook('" . $_['addressbook']["id"] . "');\" title=\"" . $l->t("Delete") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/delete.svg\"></a></td>"; + echo "<td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.showCardDAVUrl('" . OC_User::getUser() . "', '" . rawurlencode($_['addressbook']["uri"]) . "');\" title=\"" . $l->t("CardDav Link") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/public.svg\"></a></td><td width=\"20px\"><a href=\"export.php?bookid=" . $_['addressbook']["id"] . "\" title=\"" . $l->t("Download") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/download.svg\"></a></td><td width=\"20px\"><a href=\"#\" title=\"" . $l->t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\"><img class=\"svg action\" src=\"../../core/img/actions/rename.svg\"></a></td><td width=\"20px\"><a href=\"#\" onclick=\"Contacts.UI.Addressbooks.deleteAddressbook(this, '" . $_['addressbook']["id"] . "');\" title=\"" . $l->t("Delete") . "\" class=\"action\"><img class=\"svg action\" src=\"../../core/img/actions/delete.svg\"></a></td>"; diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index b90fa92c2c5..dec081a9b89 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -112,7 +112,7 @@ $id = isset($_['id']) ? $_['id'] : ''; <div id="contact_note" class="contactsection"> <form class="float" method="post"> <fieldset id="note" class="formfloat propertycontainer contactpart" data-element="NOTE"> - <textarea class="contacts_property note" name="value" cols="40" rows="10" required="required" placeholder="<?php echo $l->t('Add notes here.'); ?>"></textarea> + <textarea class="contacts_property note" name="value" cols="60" rows="15" required="required" placeholder="<?php echo $l->t('Add notes here.'); ?>"></textarea> </fieldset> </form> </div> <!-- contact_note --> diff --git a/apps/files_versioning/ajax/gethead.php b/apps/files_versioning/ajax/gethead.php deleted file mode 100644 index a0bfe77db51..00000000000 --- a/apps/files_versioning/ajax/gethead.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php -/** - * Copyright (c) 2011 Craig Roberts craig0990@googlemail.com - * This file is licensed under the Affero General Public License version 3 or - * later. - */ - - -OC_JSON::checkLoggedIn(); -// Fetch current commit (or HEAD if not yet set) -$head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); -OC_JSON::encodedPrint(array("head" => $head)); diff --git a/apps/files_versioning/ajax/sethead.php b/apps/files_versioning/ajax/sethead.php deleted file mode 100644 index dd8b924b118..00000000000 --- a/apps/files_versioning/ajax/sethead.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -/** - * Copyright (c) 2011 Craig Roberts craig0990@googlemail.com - * This file is licensed under the Affero General Public License version 3 or - * later. - */ - -OC_JSON::checkLoggedIn(); -if(isset($_POST["file_versioning_head"])){ - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $_POST["file_versioning_head"]); - OC_JSON::success(); -}else{ - OC_JSON::error(); -} diff --git a/apps/files_versioning/appinfo/app.php b/apps/files_versioning/appinfo/app.php deleted file mode 100644 index 24a8701dbb0..00000000000 --- a/apps/files_versioning/appinfo/app.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -// Include required files -require_once('apps/files_versioning/versionstorage.php'); -require_once('apps/files_versioning/versionwrapper.php'); -// Register streamwrapper for versioned:// paths -stream_wrapper_register('versioned', 'OC_VersionStreamWrapper'); - -// Add an entry in the app list for versioning and backup -OC_App::register( array( - 'order' => 10, - 'id' => 'files_versioning', - 'name' => 'Versioning and Backup' )); - -// Include stylesheets for the settings page -OC_Util::addStyle( 'files_versioning', 'settings' ); -OC_Util::addScript('files_versioning','settings'); - -// Register a settings section in the Admin > Personal page -OC_APP::registerPersonal('files_versioning','settings'); diff --git a/apps/files_versioning/appinfo/info.xml b/apps/files_versioning/appinfo/info.xml deleted file mode 100644 index b9f56f674a0..00000000000 --- a/apps/files_versioning/appinfo/info.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0"?> -<info> - <id>files_versioning</id> - <name>Versioning and Backup</name> - <licence>GPLv2</licence> - <author>Craig Roberts</author> - <require>3</require> - <description>Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind.</description> - <types> - <filesystem/> - </types> -</info> diff --git a/apps/files_versioning/css/settings.css b/apps/files_versioning/css/settings.css deleted file mode 100644 index afe2cd5508f..00000000000 --- a/apps/files_versioning/css/settings.css +++ /dev/null @@ -1,3 +0,0 @@ -#file_versioning_commit_chzn { - width: 15em; -} diff --git a/apps/files_versioning/js/settings.js b/apps/files_versioning/js/settings.js deleted file mode 100644 index 8dd13bac033..00000000000 --- a/apps/files_versioning/js/settings.js +++ /dev/null @@ -1,25 +0,0 @@ -$(document).ready(function(){ - $('#file_versioning_head').chosen(); - - $.getJSON(OC.filePath('files_versioning', 'ajax', 'gethead.php'), function(jsondata, status) { - - if (jsondata.head == 'HEAD') { - // Most recent commit, do nothing - } else { - $("#file_versioning_head").val(jsondata.head); - // Trigger the chosen update call - // See http://harvesthq.github.com/chosen/ - $("#file_versioning_head").trigger("liszt:updated"); - } - }); - - $('#file_versioning_head').change(function() { - - var data = $(this).serialize(); - $.post( OC.filePath('files_versioning', 'ajax', 'sethead.php'), data, function(data){ - if(data == 'error'){ - console.log('Saving new HEAD failed'); - } - }); - }); -}); diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php deleted file mode 100644 index 571e5cea637..00000000000 --- a/apps/files_versioning/lib_granite.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -require_once('granite/git/blob.php'); -require_once('granite/git/commit.php'); -require_once('granite/git/repository.php'); -require_once('granite/git/tag.php'); -require_once('granite/git/tree.php'); -require_once('granite/git/tree/node.php'); -require_once('granite/git/object/index.php'); -require_once('granite/git/object/raw.php'); -require_once('granite/git/object/loose.php'); -require_once('granite/git/object/packed.php'); diff --git a/apps/files_versioning/settings.php b/apps/files_versioning/settings.php deleted file mode 100644 index 94af587a215..00000000000 --- a/apps/files_versioning/settings.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -// Get the full path to the repository folder (FIXME: hard-coded to 'Backup') -$path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') - . DIRECTORY_SEPARATOR - . OC_User::getUser() - . DIRECTORY_SEPARATOR - . 'files' - . DIRECTORY_SEPARATOR - . 'Backup' - . DIRECTORY_SEPARATOR - . '.git' - . DIRECTORY_SEPARATOR; - -$repository = new Granite\Git\Repository($path); - -$commits = array(); -// Fetch most recent 50 commits (FIXME - haven't tested this much) -$commit = $repository->head(); -for ($i = 0; $i < 50; $i++) { - $commits[] = $commit; - $parents = $commit->parents(); - if (count($parents) > 0) { - $parent = $parents[0]; - } else { - break; - } - - $commit = $repository->factory('commit', $parent); -} - -$tmpl = new OC_Template( 'files_versioning', 'settings'); -$tmpl->assign('commits', $commits); -return $tmpl->fetchPage(); diff --git a/apps/files_versioning/templates/settings.php b/apps/files_versioning/templates/settings.php deleted file mode 100644 index 17f4cc7f77f..00000000000 --- a/apps/files_versioning/templates/settings.php +++ /dev/null @@ -1,12 +0,0 @@ -<fieldset id="status_list" class="personalblock"> - <strong>Versioning and Backup</strong><br> - <p><em>Please note: Backing up large files (around 16MB+) will cause your backup history to grow very large, very quickly.</em></p> - <label class="bold">Backup Folder</label> - <select name="file_versioning_head" id="file_versioning_head"> - <?php - foreach ($_['commits'] as $commit): - echo '<option value="' . $commit->sha() . '">' . $commit->message() . '</option>'; - endforeach; - ?> - </select> -</fieldset> diff --git a/apps/files_versioning/versionstorage.php b/apps/files_versioning/versionstorage.php deleted file mode 100644 index d083e623df9..00000000000 --- a/apps/files_versioning/versionstorage.php +++ /dev/null @@ -1,386 +0,0 @@ -<?php -/** - * ownCloud file storage implementation for Git repositories - * @author Craig Roberts - * @copyright 2012 Craig Roberts craig0990@googlemail.com - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - */ - -// Include Granite -require_once('lib_granite.php'); - -// Create a top-level 'Backup' directory if it does not already exist -$user = OC_User::getUser(); -if (OC_Filesystem::$loaded and !OC_Filesystem::is_dir('/Backup')) { - OC_Filesystem::mkdir('/Backup'); - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); -} - -// Generate the repository path (currently using 'full' repositories, as opposed to bare ones) -$repo_path = DIRECTORY_SEPARATOR - . OC_User::getUser() - . DIRECTORY_SEPARATOR - . 'files' - . DIRECTORY_SEPARATOR - . 'Backup'; - -// Mount the 'Backup' folder using the versioned storage provider below -OC_Filesystem::mount('OC_Filestorage_Versioned', array('repo'=>$repo_path), $repo_path . DIRECTORY_SEPARATOR); - -class OC_Filestorage_Versioned extends OC_Filestorage { - - /** - * Holds an instance of Granite\Git\Repository - */ - protected $repo; - - /** - * Constructs a new OC_Filestorage_Versioned instance, expects an associative - * array with a `repo` key set to the path of the repository's `.git` folder - * - * @param array $parameters An array containing the key `repo` pointing to the - * repository path. - */ - public function __construct($parameters) { - // Get the full path to the repository folder - $path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') - . $parameters['repo'] - . DIRECTORY_SEPARATOR - . '.git' - . DIRECTORY_SEPARATOR; - - try { - // Attempt to load the repository - $this->repo = new Granite\Git\Repository($path); - } catch (InvalidArgumentException $e) { - // $path is not a valid Git repository, we must create one - Granite\Git\Repository::init($path); - - // Load the newly-initialised repository - $this->repo = new Granite\Git\Repository($path); - - /** - * Create an initial commit with a README file - * FIXME: This functionality should be transferred to the Granite library - */ - $blob = new Granite\Git\Blob($this->repo->path()); - $blob->content('Your Backup directory is now ready for use.'); - - // Create a new tree to hold the README file - $tree = $this->repo->factory('tree'); - // Create a tree node to represent the README blob - $tree_node = new Granite\Git\Tree\Node('README', '100644', $blob->sha()); - $tree->nodes(array($tree_node->name() => $tree_node)); - - // Create an initial commit - $commit = new Granite\Git\Commit($this->repo->path()); - $user_string = OC_User::getUser() . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message('Initial commit'); - $commit->tree($tree); - - // Write it all to disk - $blob->write(); - $tree->write(); - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - } - - // Update the class pointer to the HEAD - $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); - - // Load the most recent commit if the preference is not set - if ($head == 'HEAD') { - $this->head = $this->repo->head()->sha(); - } else { - $this->head = $head; - } - } - - public function mkdir($path) { - if (mkdir("versioned:/{$this->repo->path()}$path#{$this->head}")) { - $this->head = $this->repo->head()->sha(); - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $head); - return true; - } - - return false; - } - - public function rmdir($path) { - - } - - /** - * Returns a directory handle to the requested path, or FALSE on failure - * - * @param string $path The directory path to open - * - * @return boolean|resource A directory handle, or FALSE on failure - */ - public function opendir($path) { - return opendir("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns TRUE if $path is a directory, or FALSE if not - * - * @param string $path The path to check - * - * @return boolean - */ - public function is_dir($path) { - return $this->filetype($path) == 'dir'; - } - - /** - * Returns TRUE if $path is a file, or FALSE if not - * - * @param string $path The path to check - * - * @return boolean - */ - public function is_file($path) { - return $this->filetype($path) == 'file'; - } - - public function stat($path) - { - return stat("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns the strings 'dir' or 'file', depending on the type of $path - * - * @param string $path The path to check - * - * @return string Returns 'dir' if a directory, 'file' otherwise - */ - public function filetype($path) { - if ($path == "" || $path == "/") { - return 'dir'; - } else { - if (substr($path, -1) == '/') { - $path = substr($path, 0, -1); - } - - $node = $this->tree_search($this->repo, $this->repo->factory('commit', $this->head)->tree(), $path); - - // Does it exist, or is it new? - if ($node == null) { - // New file - return 'file'; - } else { - // Is it a tree? - try { - $this->repo->factory('tree', $node); - return 'dir'; - } catch (InvalidArgumentException $e) { - // Nope, must be a blob - return 'file'; - } - } - } - } - - public function filesize($path) { - return filesize("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns a boolean value representing whether $path is readable - * - * @param string $path The path to check - *( - * @return boolean Whether or not the path is readable - */ - public function is_readable($path) { - return true; - } - - /** - * Returns a boolean value representing whether $path is writable - * - * @param string $path The path to check - *( - * @return boolean Whether or not the path is writable - */ - public function is_writable($path) { - - $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); - if ($head !== 'HEAD' && $head !== $this->repo->head()->sha()) { - // Cannot modify previous commits - return false; - } - return true; - } - - /** - * Returns a boolean value representing whether $path exists - * - * @param string $path The path to check - *( - * @return boolean Whether or not the path exists - */ - public function file_exists($path) { - return file_exists("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns an integer value representing the inode change time - * (NOT IMPLEMENTED) - * - * @param string $path The path to check - *( - * @return int Timestamp of the last inode change - */ - public function filectime($path) { - return -1; - } - - /** - * Returns an integer value representing the file modification time - * - * @param string $path The path to check - *( - * @return int Timestamp of the last file modification - */ - public function filemtime($path) { - return filemtime("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - public function file_get_contents($path) { - return file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - public function file_put_contents($path, $data) { - $success = file_put_contents("versioned:/{$this->repo->path()}$path#{$this->head}", $data); - if ($success !== false) { - // Update the HEAD in the preferences - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $this->repo->head()->sha()); - return $success; - } - - return false; - } - - public function unlink($path) { - - } - - public function rename($path1, $path2) { - - } - - public function copy($path1, $path2) { - - } - - public function fopen($path, $mode) { - return fopen("versioned:/{$this->repo->path()}$path#{$this->head}", $mode); - } - - public function getMimeType($path) { - if ($this->filetype($path) == 'dir') { - return 'httpd/unix-directory'; - } elseif ($this->filesize($path) == 0) { - // File's empty, returning text/plain allows opening in the web editor - return 'text/plain'; - } else { - $finfo = new finfo(FILEINFO_MIME_TYPE); - /** - * We need to represent the repository path, the file path, and the - * revision, which can be simply achieved with a convention of using - * `.git` in the repository directory (bare or not) and the '#part' - * segment of a URL to specify the revision. For example - * - * versioned://var/www/myrepo.git/docs/README.md#HEAD ('bare' repo) - * versioned://var/www/myrepo/.git/docs/README.md#HEAD ('full' repo) - * versioned://var/www/myrepo/.git/docs/README.md#6a8f...8a54 ('full' repo and SHA-1 commit ID) - */ - $mime = $finfo->buffer(file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}")); - return $mime; - } - } - - /** - * Generates a hash based on the file contents - * - * @param string $type The hashing algorithm to use (e.g. 'md5', 'sha256', etc.) - * @param string $path The file to be hashed - * @param boolean $raw Outputs binary data if true, lowercase hex digits otherwise - * - * @return string Hashed string representing the file contents - */ - public function hash($type, $path, $raw) { - return hash($type, file_get_contents($path), $raw); - } - - public function free_space($path) { - } - - public function search($query) { - - } - - public function touch($path, $mtime=null) { - - } - - - public function getLocalFile($path) { - } - - /** - * Recursively searches a tree for a path, returning FALSE if is not found - * or an SHA-1 id if it is found. - * - * @param string $repo The repository containing the tree object - * @param string $tree The tree object to search - * @param string $path The path to search for (relative to the tree) - * @param int $depth The depth of the current search (for recursion) - * - * @return string|boolean The SHA-1 id of the sub-tree - */ - private function tree_search($repo, $tree, $path, $depth = 0) - { - $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); - - $current_path = $paths[$depth]; - - $nodes = $tree->nodes(); - foreach ($nodes as $node) { - if ($node->name() == $current_path) { - - if (count($paths)-1 == $depth) { - // Stop, found it - return $node->sha(); - } - - // Recurse if necessary - if ($node->isDirectory()) { - $tree = $this->repo->factory('tree', $node->sha()); - return $this->tree_search($repo, $tree, $path, $depth + 1); - } - } - } - - return false; - } - -} diff --git a/apps/files_versioning/versionwrapper.php b/apps/files_versioning/versionwrapper.php deleted file mode 100644 index b83a4fd3b22..00000000000 --- a/apps/files_versioning/versionwrapper.php +++ /dev/null @@ -1,686 +0,0 @@ -<?php - -final class OC_VersionStreamWrapper { - - /** - * Determines whether or not to log debug messages with `OC_Log::write()` - */ - private $debug = true; - - /** - * The name of the ".empty" files created in new directories - */ - const EMPTYFILE = '.empty'; - - /** - * Stores the current position for `readdir()` etc. calls - */ - private $dir_position = 0; - - /** - * Stores the current position for `fread()`, `fseek()` etc. calls - */ - private $file_position = 0; - - /** - * Stores the current directory tree for `readdir()` etc. directory traversal - */ - private $tree; - - /** - * Stores the current file for `fread()`, `fseek()`, etc. calls - */ - private $blob; - - /** - * Stores the current commit for `fstat()`, `stat()`, etc. calls - */ - private $commit; - - /** - * Stores the current path for `fwrite()`, `file_put_contents()` etc. calls - */ - private $path; - - /** - * Close directory handle - */ - public function dir_closedir() { - unset($this->tree); - return true; - } - - /** - * Open directory handle - */ - public function dir_opendir($path, $options) { - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - if ($repo_file == '' || $repo_file == '/') { - // Set the tree property for the future `readdir()` etc. calls - $this->tree = array_values($this->commit->tree()->nodes()); - return true; - } elseif ($this->tree_search($this->repo, $this->commit->tree(), $repo_file) !== false) { - // Something exists at this path, is it a directory though? - try { - $tree = $this->repo->factory( - 'tree', - $this->tree_search($this->repo, $this->commit->tree(), $repo_file) - ); - $this->tree = array_values($tree->nodes()); - return true; - } catch (InvalidArgumentException $e) { - // Trying to call `opendir()` on a file, return false below - } - } - - // Unable to find the directory, return false - return false; - } - - /** - * Read entry from directory handle - */ - public function dir_readdir() { - return isset($this->tree[$this->dir_position]) - ? $this->tree[$this->dir_position++]->name() - : false; - } - - /** - * Rewind directory handle - */ - public function dir_rewinddir() { - $this->dir_position = 0; - } - - /** - * Create a directory - * Git doesn't track empty directories, so a ".empty" file is added instead - */ - public function mkdir($path, $mode, $options) { - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - // Create an empty file for Git - $empty = new Granite\Git\Blob($this->repo->path()); - $empty->content(''); - $empty->write(); - - if (dirname($repo_file) == '.') { - // Adding a new directory to the root tree - $tree = $this->repo->head()->tree(); - } else { - $tree = $this->repo->factory('tree', $this->tree_search( - $this->repo, $this->repo->head()->tree(), dirname($repo_file) - ) - ); - } - - // Create our new tree, with our empty file - $dir = $this->repo->factory('tree'); - $nodes = array(); - $nodes[self::EMPTYFILE] = new Granite\Git\Tree\Node(self::EMPTYFILE, '100644', $empty->sha()); - $dir->nodes($nodes); - $dir->write(); - - // Add our new tree to its parent - $nodes = $tree->nodes(); - $nodes[basename($repo_file)] = new Granite\Git\Tree\Node(basename($repo_file), '040000', $dir->sha()); - $tree->nodes($nodes); - $tree->write(); - - // We need to recursively update each parent tree, since they are all - // hashed and the changes will cascade back up the chain - - // So, we're currently at the bottom-most directory - $current_dir = dirname($repo_file); - $previous_tree = $tree; - - if ($current_dir !== '.') { - do { - // Determine the parent directory - $previous_dir = $current_dir; - $current_dir = dirname($current_dir); - - $current_tree = $current_dir !== '.' - ? $this->repo->factory( - 'tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - $current_dir - ) - ) - : $this->repo->head()->tree(); - - $current_nodes = $current_tree->nodes(); - $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( - basename($previous_dir), '040000', $previous_tree->sha() - ); - $current_tree->nodes($current_nodes); - $current_tree->write(); - - $previous_tree = $current_tree; - } while ($current_dir !== '.'); - - $tree = $previous_tree; - } - - // Create a new commit to represent this write - $commit = $this->repo->factory('commit'); - $username = OC_User::getUser(); - $user_string = $username . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message("$username created the `$repo_file` directory, " . date('d F Y H:i', time()) . '.'); - $commit->parents(array($this->repo->head()->sha())); - $commit->tree($tree); - - // Write it to disk - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - - return true; - } - - /** - * Renames a file or directory - */ - public function rename($path_from, $path_to) { - - } - - /** - * Removes a directory - */ - public function rmdir($path, $options) { - - } - - /** - * Retrieve the underlaying resource (NOT IMPLEMENTED) - */ - public function stream_cast($cast_as) { - return false; - } - - /** - * Close a resource - */ - public function stream_close() { - unset($this->blob); - return true; - } - - /** - * Tests for end-of-file on a file pointer - */ - public function stream_eof() { - return !($this->file_position < strlen($this->blob)); - } - - /** - * Flushes the output (NOT IMPLEMENTED) - */ - public function stream_flush() { - return false; - } - - /** - * Advisory file locking (NOT IMPLEMENTED) - */ - public function stream_lock($operation) { - return false; - } - - /** - * Change stream options (NOT IMPLEMENTED) - * Called in response to `chgrp()`, `chown()`, `chmod()` and `touch()` - */ - public function stream_metadata($path, $option, $var) { - return false; - } - - /** - * Opens file or URL - */ - public function stream_open($path, $mode, $options, &$opened_path) { - // Store the path, so we can use it later in `stream_write()` if necessary - $this->path = $path; - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - $file = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); - if ($file !== false) { - try { - $this->blob = $this->repo->factory('blob', $file)->content(); - return true; - } catch (InvalidArgumentException $e) { - // Trying to open a directory, return false below - } - } elseif ($mode !== 'r') { - // All other modes allow opening for reading and writing, clearly - // some 'write' files may not exist yet... - return true; - } - - // File could not be found or is not actually a file - return false; - } - - /** - * Read from stream - */ - public function stream_read($count) { - // Fetch the remaining set of bytes - $bytes = substr($this->blob, $this->file_position, $count); - - // If EOF or empty string, return false - if ($bytes == '' || $bytes == false) { - return false; - } - - // If $count does not extend past EOF, add $count to stream offset - if ($this->file_position + $count < strlen($this->blob)) { - $this->file_position += $count; - } else { - // Otherwise return all remaining bytes - $this->file_position = strlen($this->blob); - } - - return $bytes; - } - - /** - * Seeks to specific location in a stream - */ - public function stream_seek($offset, $whence = SEEK_SET) { - $new_offset = false; - - switch ($whence) - { - case SEEK_SET: - $new_offset = $offset; - break; - case SEEK_CUR: - $new_offset = $this->file_position += $offset; - break; - case SEEK_END: - $new_offset = strlen($this->blob) + $offset; - break; - } - - $this->file_position = $offset; - - return ($new_offset !== false); - } - - /** - * Change stream options (NOT IMPLEMENTED) - */ - public function stream_set_option($option, $arg1, $arg2) { - return false; - } - - /** - * Retrieve information about a file resource (NOT IMPLEMENTED) - */ - public function stream_stat() { - - } - - /** - * Retrieve the current position of a stream - */ - public function stream_tell() { - return $this->file_position; - } - - /** - * Truncate stream - */ - public function stream_truncate($new_size) { - - } - - /** - * Write to stream - * FIXME: Could use heavy refactoring - */ - public function stream_write($data) { - /** - * FIXME: This also needs to be added to Granite, in the form of `add()`, - * `rm()` and `commit()` calls - */ - - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($this->path); - - $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); - - if ($node !== false) { - // File already exists, attempting modification of existing tree - try { - $this->repo->factory('blob', $node); - - // Create our new blob with the provided $data - $blob = $this->repo->factory('blob'); - $blob->content($data); - $blob->write(); - - // We know the tree exists, so strip the filename from the path and - // find it... - - if (dirname($repo_file) == '.' || dirname($repo_file) == '') { - // Root directory - $tree = $this->repo->head()->tree(); - } else { - // Sub-directory - $tree = $this->repo->factory('tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - dirname($repo_file) - ) - ); - } - - // Replace the old blob with our newly modified one - $tree_nodes = $tree->nodes(); - $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( - basename($repo_file), '100644', $blob->sha() - ); - $tree->nodes($tree_nodes); - $tree->write(); - - // We need to recursively update each parent tree, since they are all - // hashed and the changes will cascade back up the chain - - // So, we're currently at the bottom-most directory - $current_dir = dirname($repo_file); - $previous_tree = $tree; - - if ($current_dir !== '.') { - do { - // Determine the parent directory - $previous_dir = $current_dir; - $current_dir = dirname($current_dir); - - $current_tree = $current_dir !== '.' - ? $this->repo->factory( - 'tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - $current_dir - ) - ) - : $this->repo->head()->tree(); - - $current_nodes = $current_tree->nodes(); - $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( - basename($previous_dir), '040000', $previous_tree->sha() - ); - $current_tree->nodes($current_nodes); - $current_tree->write(); - - $previous_tree = $current_tree; - } while ($current_dir !== '.'); - } - - // Create a new commit to represent this write - $commit = $this->repo->factory('commit'); - $username = OC_User::getUser(); - $user_string = $username . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message("$username modified the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); - $commit->parents(array($this->repo->head()->sha())); - $commit->tree($previous_tree); - - // Write it to disk - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - - // If we made it this far, write was successful - update the stream - // position and return the number of bytes written - $this->file_position += strlen($data); - return strlen($data); - - } catch (InvalidArgumentException $e) { - // Attempting to write to a directory or other error, fail - return 0; - } - } else { - // File does not exist, needs to be created - - // Create our new blob with the provided $data - $blob = $this->repo->factory('blob'); - $blob->content($data); - $blob->write(); - - if (dirname($repo_file) == '.') { - // Trying to add a new file to the root tree, nice and easy - $tree = $this->repo->head()->tree(); - $tree_nodes = $tree->nodes(); - $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( - basename($repo_file), '100644', $blob->sha() - ); - $tree->nodes($tree_nodes); - $tree->write(); - } else { - // Trying to add a new file to a subdirectory, try and find it - $tree = $this->repo->factory('tree', $this->tree_search( - $this->repo, $this->repo->head()->tree(), dirname($repo_file) - ) - ); - - // Add the blob to the tree - $nodes = $tree->nodes(); - $nodes[basename($repo_file)] = new Granite\Git\Tree\Node( - basename($repo_file), '100644', $blob->sha() - ); - $tree->nodes($nodes); - $tree->write(); - - // We need to recursively update each parent tree, since they are all - // hashed and the changes will cascade back up the chain - - // So, we're currently at the bottom-most directory - $current_dir = dirname($repo_file); - $previous_tree = $tree; - - if ($current_dir !== '.') { - do { - // Determine the parent directory - $previous_dir = $current_dir; - $current_dir = dirname($current_dir); - - $current_tree = $current_dir !== '.' - ? $this->repo->factory( - 'tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - $current_dir - ) - ) - : $this->repo->head()->tree(); - - $current_nodes = $current_tree->nodes(); - $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( - basename($previous_dir), '040000', $previous_tree->sha() - ); - $current_tree->nodes($current_nodes); - $current_tree->write(); - - $previous_tree = $current_tree; - } while ($current_dir !== '.'); - - $tree = $previous_tree; - } - } - - // Create a new commit to represent this write - $commit = $this->repo->factory('commit'); - $username = OC_User::getUser(); - $user_string = $username . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message("$username created the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); - $commit->parents(array($this->repo->head()->sha())); - $commit->tree($tree); // Top-level tree (NOT the newly modified tree) - - // Write it to disk - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - - // If we made it this far, write was successful - update the stream - // position and return the number of bytes written - $this->file_position += strlen($data); - return strlen($data); - } - - // Write failed - return 0; - } - - /** - * Delete a file - */ - public function unlink($path) { - - } - - /** - * Retrieve information about a file - */ - public function url_stat($path, $flags) { - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); - - if ($node == false && $this->commit->sha() == $this->repo->head()->sha()) { - // A new file - no information available - $size = 0; - $mtime = -1; - } else { - - // Is it a directory? - try { - $this->repo->factory('tree', $node); - $size = 4096; // FIXME - } catch (InvalidArgumentException $e) { - // Must be a file - $size = strlen(file_get_contents($path)); - } - - // Parse the timestamp from the commit message - preg_match('/[0-9]{10}+/', $this->commit->committer(), $matches); - $mtime = $matches[0]; - } - - $stat["dev"] = ""; - $stat["ino"] = ""; - $stat["mode"] = ""; - $stat["nlink"] = ""; - $stat["uid"] = ""; - $stat["gid"] = ""; - $stat["rdev"] = ""; - $stat["size"] = $size; - $stat["atime"] = $mtime; - $stat["mtime"] = $mtime; - $stat["ctime"] = $mtime; - $stat["blksize"] = ""; - $stat["blocks"] = ""; - - return $stat; - } - - /** - * Debug function for development purposes - */ - private function debug($message, $level = OC_Log::DEBUG) - { - if ($this->debug) { - OC_Log::write('files_versioning', $message, $level); - } - } - - /** - * Parses a URL of the form: - * `versioned://path/to/git/repository/.git/path/to/file#SHA-1-commit-id` - * FIXME: Will throw an InvalidArgumentException if $path is invaid - * - * @param string $path The path to parse - * - * @return array An array containing an instance of Granite\Git\Repository, - * the file path, and an instance of Granite\Git\Commit - * @throws InvalidArgumentException If the repository cannot be loaded - */ - private function parse_url($path) - { - preg_match('/\/([A-Za-z0-9\/]+\.git\/)([A-Za-z0-9\/\.\/]*)(#([A-Fa-f0-9]+))*/', $path, $matches); - - // Load up the repo - $repo = new \Granite\Git\Repository($matches[1]); - // Parse the filename (stripping any trailing slashes) - $repo_file = $matches[2]; - if (substr($repo_file, -1) == '/') { - $repo_file = substr($repo_file, 0, -1); - } - - // Default to HEAD if no commit is provided - $repo_commit = isset($matches[4]) - ? $matches[4] - : $repo->head()->sha(); - - // Load the relevant commit - $commit = $repo->factory('commit', $repo_commit); - - return array($repo, $repo_file, $commit); - } - - /** - * Recursively searches a tree for a path, returning FALSE if is not found - * or an SHA-1 id if it is found. - * - * @param string $repo The repository containing the tree object - * @param string $tree The tree object to search - * @param string $path The path to search for (relative to the tree) - * @param int $depth The depth of the current search (for recursion) - * - * @return string|boolean The SHA-1 id of the sub-tree - */ - private function tree_search($repo, $tree, $path, $depth = 0) - { - $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); - - $current_path = $paths[$depth]; - - $nodes = $tree->nodes(); - foreach ($nodes as $node) { - if ($node->name() == $current_path) { - - if (count($paths)-1 == $depth) { - // Stop, found it - return $node->sha(); - } - - // Recurse if necessary - if ($node->isDirectory()) { - $tree = $this->repo->factory('tree', $node->sha()); - return $this->tree_search($repo, $tree, $path, $depth + 1); - } - } - } - - return false; - } - -} diff --git a/apps/files_versions/appinfo/app.php b/apps/files_versions/appinfo/app.php new file mode 100644 index 00000000000..6e7a803252e --- /dev/null +++ b/apps/files_versions/appinfo/app.php @@ -0,0 +1,19 @@ +<?php + +require_once('apps/files_versions/versions.php'); + +// Add an entry in the app list +OC_App::register( array( + 'order' => 10, + 'id' => 'files_versions', + 'name' => 'Versioning' )); + +OC_APP::registerAdmin('files_versions', 'settings'); + +// Listen to write signals +OC_Hook::connect(OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, "OCA_Versions\Storage", "write_hook"); + + + + +?> diff --git a/apps/files_versions/appinfo/info.xml b/apps/files_versions/appinfo/info.xml new file mode 100644 index 00000000000..9936a2ad8b2 --- /dev/null +++ b/apps/files_versions/appinfo/info.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<info> + <id>files_versions</id> + <name>Versions</name> + <licence>AGPL</licence> + <author>Frank Karlitschek</author> + <require>3</require> + <description>Versioning of files</description> + <types> + <filesystem/> + </types> +</info> diff --git a/apps/files_versioning/appinfo/version b/apps/files_versions/appinfo/version index afaf360d37f..afaf360d37f 100644 --- a/apps/files_versioning/appinfo/version +++ b/apps/files_versions/appinfo/version diff --git a/apps/files_versions/css/versions.css b/apps/files_versions/css/versions.css new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ b/apps/files_versions/css/versions.css @@ -0,0 +1,2 @@ + + diff --git a/apps/files_versions/history.php b/apps/files_versions/history.php new file mode 100644 index 00000000000..6c7626ca4ed --- /dev/null +++ b/apps/files_versions/history.php @@ -0,0 +1,60 @@ +<?php + +/** + * ownCloud - History page of the Versions App + * + * @author Frank Karlitschek + * @copyright 2011 Frank Karlitschek karlitschek@kde.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +require_once('../../lib/base.php'); + +OC_Util::checkLoggedIn(); + +if (isset($_GET['path'])) { + + $path = $_GET['path']; + $path = strip_tags($path); + + // roll back to old version if button clicked + if(isset($_GET['revert'])) { + \OCA_Versions\Storage::rollback($path,$_GET['revert']); + } + + // show the history only if there is something to show + if(OCA_Versions\Storage::isversioned($path)) { + + $count=5; //show the newest revisions + $versions=OCA_Versions\Storage::getversions($path,$count); + + $tmpl = new OC_Template('files_versions', 'history', 'user'); + $tmpl->assign('path', $path); + $tmpl->assign('versions', array_reverse($versions)); + $tmpl->printPage(); + }else{ + $tmpl = new OC_Template('files_versions', 'history', 'user'); + $tmpl->assign('path', $path); + $tmpl->assign('message', 'No old versions available'); + $tmpl->printPage(); + } +}else{ + $tmpl = new OC_Template('files_versions', 'history', 'user'); + $tmpl->assign('message', 'No path specified'); + $tmpl->printPage(); +} + + +?> diff --git a/apps/files_versions/js/versions.js b/apps/files_versions/js/versions.js new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ b/apps/files_versions/js/versions.js @@ -0,0 +1,2 @@ + + diff --git a/apps/files_versions/settings.php b/apps/files_versions/settings.php new file mode 100644 index 00000000000..eb154d3edd3 --- /dev/null +++ b/apps/files_versions/settings.php @@ -0,0 +1,10 @@ +<?php + +OC_Util::checkAdminUser(); + +OC_Util::addScript( 'files_versions', 'versions' ); + +$tmpl = new OC_Template( 'files_versions', 'settings'); + +return $tmpl->fetchPage(); +?> diff --git a/apps/files_versions/templates/history.php b/apps/files_versions/templates/history.php new file mode 100644 index 00000000000..1b3de9ce77c --- /dev/null +++ b/apps/files_versions/templates/history.php @@ -0,0 +1,18 @@ +<?php + if(isset($_['message'])){ + + + if(isset($_['path'])) echo('<strong>File: '.$_['path']).'</strong><br>'; + echo('<strong>'.$_['message']).'</strong><br>'; + + }else{ + + echo('<strong>Versions of '.$_['path']).'</strong><br>'; + echo('<p><em>You can click on the revert button to revert to the specific verson.</em></p><br />'); + foreach ($_['versions'] as $v){ + echo(' '.OC_Util::formatDate($v).' <a href="history.php?path='.urlencode($_['path']).'&revert='.$v.'" class="button">revert</a><br /><br />'); + } + + } + +?> diff --git a/apps/files_versions/templates/settings.php b/apps/files_versions/templates/settings.php new file mode 100644 index 00000000000..8c8def94429 --- /dev/null +++ b/apps/files_versions/templates/settings.php @@ -0,0 +1,7 @@ +<form id="external"> + <fieldset class="personalblock"> + <strong>Versions</strong><br /> + + Configuration goes here... + </fieldset> +</form> diff --git a/apps/files_versions/versions.php b/apps/files_versions/versions.php new file mode 100644 index 00000000000..156a4f59c73 --- /dev/null +++ b/apps/files_versions/versions.php @@ -0,0 +1,216 @@ +<?php +/** + * Copyright (c) 2012 Frank Karlitschek <frank@owncloud.org> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Versions + * + * A class to handle the versioning of files. + */ + +namespace OCA_Versions; + +class Storage { + + + // config.php configuration: + // - files_versions + // - files_versionsfolder + // - files_versionsblacklist + // - files_versionsmaxfilesize + // - files_versionsinterval + // - files_versionmaxversions + // + // todo: + // - port to oc_filesystem to enable network transparency + // - check if it works well together with encryption + // - do configuration web interface + // - implement expire all function. And find a place to call it ;-) + // - add transparent compression. first test if it´s worth it. + + const DEFAULTENABLED=true; + const DEFAULTFOLDER='versions'; + const DEFAULTBLACKLIST='avi mp3 mpg mp4'; + const DEFAULTMAXFILESIZE=1048576; // 10MB + const DEFAULTMININTERVAL=300; // 5 min + const DEFAULTMAXVERSIONS=50; + + /** + * init the versioning and create the versions folder. + */ + public static function init() { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + // create versions folder + $foldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + if(!is_dir($foldername)){ + mkdir($foldername); + } + } + } + + + /** + * listen to write event. + */ + public static function write_hook($params) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $path = $params[\OC_Filesystem::signal_param_path]; + if($path<>'') Storage::store($path); + } + } + + + + /** + * store a new version of a file. + */ + public static function store($filename) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + $filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files'; + Storage::init(); + + // check if filename is a directory + if(is_dir($filesfoldername.$filename)){ + return false; + } + + // check filetype blacklist + $blacklist=explode(' ',\OC_Config::getValue('files_versionsblacklist', Storage::DEFAULTBLACKLIST)); + foreach($blacklist as $bl) { + $parts=explode('.', $filename); + $ext=end($parts); + if(strtolower($ext)==$bl) { + return false; + } + } + + // check filesize + if(filesize($filesfoldername.$filename)>\OC_Config::getValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)){ + return false; + } + + + // check mininterval + $matches=glob($versionsfoldername.$filename.'.v*'); + sort($matches); + $parts=explode('.v',end($matches)); + if((end($parts)+Storage::DEFAULTMININTERVAL)>time()){ + return false; + } + + + // create all parent folders + $info=pathinfo($filename); + @mkdir($versionsfoldername.$info['dirname'],0700,true); + + + // store a new version of a file + copy($filesfoldername.$filename,$versionsfoldername.$filename.'.v'.time()); + + // expire old revisions + Storage::expire($filename); + } + } + + + /** + * rollback to an old version of a file. + */ + public static function rollback($filename,$revision) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + $filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files'; + // rollback + @copy($versionsfoldername.$filename.'.v'.$revision,$filesfoldername.$filename); + } + } + + /** + * check if old versions of a file exist. + */ + public static function isversioned($filename) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + + // check for old versions + $matches=glob($versionsfoldername.$filename.'.v*'); + if(count($matches)>1){ + return true; + }else{ + return false; + } + }else{ + return(false); + } + } + + + + /** + * get a list of old versions of a file. + */ + public static function getversions($filename,$count=0) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + $versions=array(); + + // fetch for old versions + $matches=glob($versionsfoldername.$filename.'.v*'); + sort($matches); + foreach($matches as $ma) { + $parts=explode('.v',$ma); + $versions[]=(end($parts)); + } + + // only show the newest commits + if($count<>0 and (count($versions)>$count)) { + $versions=array_slice($versions,count($versions)-$count); + } + + return($versions); + + + }else{ + return(array()); + } + } + + + + /** + * expire old versions of a file. + */ + public static function expire($filename) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + + // check for old versions + $matches=glob($versionsfoldername.$filename.'.v*'); + if(count($matches)>\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)){ + $numbertodelete=count($matches-\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)); + + // delete old versions of a file + $deleteitems=array_slice($matches,0,$numbertodelete); + foreach($deleteitems as $de){ + unlink($versionsfoldername.$filename.'.v'.$de); + } + } + } + } + + /** + * expire all old versions. + */ + public static function expireall($filename) { + // todo this should go through all the versions directories and delete all the not needed files and not needed directories. + // useful to be included in a cleanup cronjob. + } + + +} |