├── .gitignore ├── _config.php └── code ├── UpdateForSyncroTask.php ├── controllers └── RemoteNodeAdmin.php ├── dataobjects ├── RemoteSyncroNode.php └── SyncroDelete.php ├── extensions ├── SyncroSiteConfig.php └── SyncroableExtension.php ├── jobs └── SyncrotronJob.php ├── lib └── Uuid.php ├── services ├── Syncroable.php ├── SyncrotronPermissions.php └── SyncrotronService.php ├── tests └── TestSyncrotron.php └── thirdparty ├── _manifest_exclude └── klogger ├── .gitignore ├── README.markdown ├── example └── example.php └── src └── KLogger.php /.gitignore: -------------------------------------------------------------------------------- 1 | sync-logs/ 2 | -------------------------------------------------------------------------------- /_config.php: -------------------------------------------------------------------------------- 1 | Title." ... "; 19 | $obj->write(); 20 | echo $obj->ContentID . "
\n"; 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /code/controllers/RemoteNodeAdmin.php: -------------------------------------------------------------------------------- 1 | 'Varchar(128)', 11 | 'NodeURL' => 'Varchar(128)', 12 | 'APIToken' => 'Varchar(128)', 13 | 'LastSync' => 'SS_Datetime', 14 | ); 15 | 16 | public static $summary_fields = array( 17 | 'NodeURL', 18 | 'LastSync', 19 | ); 20 | 21 | public static $searchable_fields = array( 22 | 'NodeURL', 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /code/dataobjects/SyncroDelete.php: -------------------------------------------------------------------------------- 1 | 'Varchar(128)', 13 | 'Type' => 'Varchar(128)', 14 | 'Deleted' => 'SS_Datetime', 15 | ); 16 | 17 | public static function record_delete($object) { 18 | $delete = new SyncroDelete(); 19 | $delete->ContentID = $object->ContentID; 20 | $delete->Type = $object->ClassName; 21 | $delete->Deleted = gmdate('Y-m-d H:i:s'); 22 | $delete->write(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /code/extensions/SyncroSiteConfig.php: -------------------------------------------------------------------------------- 1 | array( 12 | 'SystemID' => 'Varchar(128)' 13 | ) 14 | ); 15 | } 16 | 17 | public function onBeforeWrite() { 18 | if (!$this->owner->SystemID) { 19 | $this->owner->SystemID = Uuid::get(); 20 | } 21 | } 22 | 23 | public function getSyncroIdentifier() { 24 | if (!$this->owner->SystemID) { 25 | // write now to make sure ID is set 26 | $this->owner->write(); 27 | } 28 | return $this->owner->SystemID; 29 | } 30 | 31 | public function updateCMSFields(FieldSet &$fields) { 32 | $fields->addFieldToTab('Root.Syncro', new ReadonlyField('SystemID', _t('Syncro.SYSTEM_ID', 'System Identifier'))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /code/extensions/SyncroableExtension.php: -------------------------------------------------------------------------------- 1 | array( 21 | 'MasterNode' => 'Varchar(128)', 22 | 'ContentID' => 'Varchar(128)', 23 | 'CreatedUTC' => 'SS_Datetime', // create time on master node 24 | 'LastEditedUTC' => 'SS_Datetime', // utc last edited time on master node 25 | 'UpdatedUTC' => 'SS_Datetime', // utc last edited time on any node 26 | 'OriginalID' => 'Int', 27 | ) 28 | ); 29 | } 30 | 31 | public function onBeforeWrite() { 32 | $config = SiteConfig::current_site_config(); 33 | if (!$this->owner->MasterNode) { 34 | $this->owner->MasterNode = $config->getSyncroIdentifier(); 35 | } 36 | 37 | if (!$this->owner->ContentID) { 38 | $this->owner->ContentID = Uuid::get(); 39 | } 40 | 41 | $nowUTC = gmdate('Y-m-d H:i:s'); 42 | $this->owner->UpdatedUTC = $nowUTC; 43 | 44 | // if we're updating on the master node, change the lasteditedUTC and created UTC if needbe 45 | if ($this->owner->MasterNode == $config->getSyncroIdentifier()) { 46 | $this->owner->LastEditedUTC = $nowUTC; 47 | if (!$this->owner->CreatedUTC) { 48 | $this->owner->CreatedUTC = $nowUTC; 49 | } 50 | } 51 | } 52 | 53 | public function onAfterWrite() { 54 | if (!$this->owner->OriginalID) { 55 | $this->owner->OriginalID = $this->owner->ID; 56 | $this->owner->write(); 57 | } 58 | } 59 | 60 | public function updateFrontendFields(FieldSet $fields) { 61 | $fields->removeByName('MasterNode'); 62 | $fields->removeByName('ContentID'); 63 | $fields->removeByName('OriginalID'); 64 | $fields->removeByName('LastEditedUTC'); 65 | } 66 | 67 | public function onAfterDelete() { 68 | parent::onAfterDelete(); 69 | if ($this->owner->MasterNode == SiteConfig::current_site_config()->SystemID) { 70 | SyncroDelete::record_delete($this->owner); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /code/jobs/SyncrotronJob.php: -------------------------------------------------------------------------------- 1 | totalSteps = 1; 13 | } 14 | 15 | public function getTitle() { 16 | return 'Syncronise data from remote nodes'; 17 | } 18 | 19 | public function process() { 20 | singleton('SyncrotronService')->getUpdates(); 21 | singleton('QueuedJobService')->queueJob(new SyncrotronJob(), date('Y-m-d H:i:s', time() + self::SYNC_TIME)); 22 | 23 | $this->currentStep = $this->totalSteps; 24 | $this->isComplete = true; 25 | 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/lib/Uuid.php: -------------------------------------------------------------------------------- 1 | urand = @fopen ( '/dev/urandom', 'rb' ); 11 | } 12 | 13 | /** 14 | * @brief Generates a Universally Unique IDentifier, version 4. 15 | * 16 | * This function generates a truly random UUID. The built in CakePHP String::uuid() function 17 | * is not cryptographically secure. You should uses this function instead. 18 | * 19 | * @see http://tools.ietf.org/html/rfc4122#section-4.4 20 | * @see http://en.wikipedia.org/wiki/UUID 21 | * @return string A UUID, made up of 32 hex digits and 4 hyphens. 22 | */ 23 | function get() { 24 | 25 | $pr_bits = false; 26 | if (is_a ( $this, 'uuid' )) { 27 | if (is_resource ( $this->urand )) { 28 | $pr_bits .= @fread ( $this->urand, 16 ); 29 | } 30 | } 31 | if (! $pr_bits) { 32 | $fp = @fopen ( '/dev/urandom', 'rb' ); 33 | if ($fp !== false) { 34 | $pr_bits .= @fread ( $fp, 16 ); 35 | @fclose ( $fp ); 36 | } else { 37 | // If /dev/urandom isn't available (eg: in non-unix systems), use mt_rand(). 38 | $pr_bits = ""; 39 | for($cnt = 0; $cnt < 16; $cnt ++) { 40 | $pr_bits .= chr ( mt_rand ( 0, 255 ) ); 41 | } 42 | } 43 | } 44 | $time_low = bin2hex ( substr ( $pr_bits, 0, 4 ) ); 45 | $time_mid = bin2hex ( substr ( $pr_bits, 4, 2 ) ); 46 | $time_hi_and_version = bin2hex ( substr ( $pr_bits, 6, 2 ) ); 47 | $clock_seq_hi_and_reserved = bin2hex ( substr ( $pr_bits, 8, 2 ) ); 48 | $node = bin2hex ( substr ( $pr_bits, 10, 6 ) ); 49 | 50 | /** 51 | * Set the four most significant bits (bits 12 through 15) of the 52 | * time_hi_and_version field to the 4-bit version number from 53 | * Section 4.1.3. 54 | * @see http://tools.ietf.org/html/rfc4122#section-4.1.3 55 | */ 56 | $time_hi_and_version = hexdec ( $time_hi_and_version ); 57 | $time_hi_and_version = $time_hi_and_version >> 4; 58 | $time_hi_and_version = $time_hi_and_version | 0x4000; 59 | 60 | /** 61 | * Set the two most significant bits (bits 6 and 7) of the 62 | * clock_seq_hi_and_reserved to zero and one, respectively. 63 | */ 64 | $clock_seq_hi_and_reserved = hexdec ( $clock_seq_hi_and_reserved ); 65 | $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2; 66 | $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000; 67 | 68 | return sprintf ( '%08s-%04s-%04x-%04x-%012s', $time_low, $time_mid, $time_hi_and_version, $clock_seq_hi_and_reserved, $node ); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /code/services/Syncroable.php: -------------------------------------------------------------------------------- 1 | dataService = singleton('DataService'); 52 | $this->log = new KLogger(SYNCROTRON_LOGDIR, KLogger::INFO); 53 | } 54 | 55 | /** 56 | * The list of methods accessible as webservices. 57 | * 58 | * @return array 59 | */ 60 | public function webEnabledMethods() { 61 | return array( 62 | 'listUpdates' => 'GET', 63 | ); 64 | } 65 | 66 | /** 67 | * Set strict access 68 | * 69 | * @param boolean $v 70 | */ 71 | public function setStrictAccess($v) { 72 | $this->strictAccess = $v; 73 | } 74 | 75 | /** 76 | * Set the filter date value 77 | * @param string $d 78 | */ 79 | public function setFilterDate($d) { 80 | if ($d == 'UpdatedUTC' || $d == 'LastEditedUTC') { 81 | $this->filterDate = $d; 82 | } 83 | } 84 | 85 | /** 86 | * Lists all updated data objects since a particular date that the caller would be interested in 87 | * 88 | * @param date $since 89 | * 90 | * @return DataObjectSet 91 | * The list of data objects that have been created/changed since 92 | * 93 | */ 94 | public function listUpdates($since, $system) { 95 | if (!preg_match('/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $since)) { 96 | throw new Exception("Invalid date"); 97 | } 98 | 99 | $since = Convert::raw2sql($since); 100 | $system = Convert::raw2sql($system); 101 | $typesToSync = ClassInfo::implementorsOf('Syncroable'); 102 | 103 | // restrict to only those items we have been granted sync rights to 104 | $requiredPerm = $this->strictAccess ? 'Syncro' : 'View'; 105 | 106 | // we only get objects edited SINCE a certain time 107 | // and that didn't originate in the remote server 108 | 109 | $filter = '"' . Convert::raw2sql($this->filterDate) .'" >= \'' . $since . '\' AND "MasterNode" <> \''. $system .'\''; 110 | 111 | $allUpdates = array(); 112 | foreach ($typesToSync as $type) { 113 | $objects = $this->dataService->getAll($type, $filter, '"LastEditedUTC" ASC', "", "", "DataObjectSet", $requiredPerm); 114 | if ($objects && $objects->count()) { 115 | foreach ($objects as $object) { 116 | $toSync = $object->forSyncro(); 117 | // some that we force 118 | // note that UpdatedUTC is not sent as it is always a local node specific date 119 | $toSync['MasterNode'] = $object->MasterNode; 120 | $toSync['ContentID'] = $object->ContentID; 121 | $toSync['ClassName'] = $object->ClassName; 122 | $toSync['LastEditedUTC'] = $object->LastEditedUTC; 123 | $toSync['CreatedUTC'] = $object->CreatedUTC; 124 | $toSync['OriginalID'] = $object->OriginalID; 125 | 126 | $allUpdates[] = $toSync; 127 | } 128 | } 129 | } 130 | 131 | $deletes = DataObject::get('SyncroDelete', '"Deleted" >= \'' . $since . '\''); 132 | if ($deletes && $deletes->count()) { 133 | foreach ($deletes as $del) { 134 | $del = array( 135 | 'SYNCRODELETE' => 'DELETE', 136 | 'ContentID' => $del->ContentID, 137 | 'Type' => $del->Type, 138 | 'MasterNode' => SiteConfig::current_site_config()->SystemID, 139 | ); 140 | $allUpdates[] = $del; 141 | } 142 | } 143 | 144 | return $allUpdates; 145 | } 146 | 147 | /** 148 | * Get updates from the remote systems 149 | */ 150 | public function getUpdates() { 151 | $config = SiteConfig::current_site_config(); 152 | $systemId = $config->getSyncroIdentifier(); 153 | $nodes = $this->dataService->getAll('RemoteSyncroNode'); 154 | foreach ($nodes as $node) { 155 | $url = $node->NodeURL .'/' . self::SERVICE_URL; 156 | $lastSync = $node->LastSync ? $node->LastSync : '2012-01-01 00:00:00'; 157 | 158 | $params = array( 159 | 'token' => $node->APIToken, 160 | 'since' => $lastSync, 161 | 'system' => $systemId, 162 | 'rand' => mt_rand(0, 22) 163 | ); 164 | 165 | $svc = new RestfulService($url); 166 | $svc->setQueryString($params); 167 | $response = $svc->request(); 168 | if ($response->isError()) { 169 | // log and proceed 170 | return; 171 | throw new Exception("Request failed to $url"); 172 | } 173 | 174 | 175 | $response = $response->getBody(); 176 | 177 | if (is_string($response) && strlen($response)) { 178 | 179 | $data = json_decode($response); 180 | if ($data && is_array($data->response)) { 181 | $this->log->logInfo("Loading " . count($data->response) . " objects from " . $node->NodeURL); 182 | $updateTime = null; 183 | foreach ($data->response as $item) { 184 | $this->processUpdatedObject($item); 185 | if ($item->Title) { 186 | $this->log->logInfo("Sync'd $item->ClassName #$item->ID '" . $item->Title . "'"); 187 | } else { 188 | $this->log->logInfo("Sync'd $item->ClassName #$item->ID"); 189 | } 190 | if ($item->LastEditedUTC || $item->UpdatedUTC) { 191 | $timeInt = strtotime($item->LastEditedUTC); 192 | if ($timeInt > $updateTime) { 193 | $updateTime = $timeInt; 194 | } 195 | $timeInt = strtotime($item->UpdatedUTC); 196 | if ($timeInt > $updateTime) { 197 | $updateTime = $timeInt; 198 | } 199 | } 200 | } 201 | 202 | if ($updateTime) { 203 | $node->LastSync = gmdate('Y-m-d H:i:s'); 204 | $node->write(); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | /** 212 | * process updates based on the provided array of data from the remote system 213 | * 214 | * @param type $json 215 | */ 216 | public function processUpdatedObject($object) { 217 | if ($object->ClassName && $object->ContentID && $object->MasterNode) { 218 | // explicitly use the unchecked call here because we don't want to create a new item if it actually turns 219 | // out that it exists and we just don't have write access to it 220 | $existing = DataObject::get_one($object->ClassName, '"ContentID" = \'' . Convert::raw2sql($object->ContentID).'\' AND "MasterNode" = \'' . Convert::raw2sql($object->MasterNode) .'\''); 221 | if ($existing && $existing->exists()) { 222 | if (!$existing->checkPerm('Write')) { 223 | // should we throw an error here? 224 | return; 225 | } 226 | } else { 227 | $cls = $object->ClassName; 228 | $existing = new $cls; 229 | if (isset($object->Title)) { 230 | $existing->Title = $object->Title; 231 | } 232 | // need to initially write, because we're going to totally change the values afterwards 233 | $existing->write(); 234 | 235 | $existing->ContentID = $object->ContentID; 236 | $existing->MasterNode = $object->MasterNode; 237 | $existing->OriginalID = $object->OriginalID; 238 | $existing->CreatedUTC = $object->CreatedUTC; 239 | } 240 | 241 | $existing->LastEditedUTC = $object->LastEditedUTC; 242 | $existing->fromSyncro($object); 243 | $existing->write(); 244 | } else if ($object->SYNCRODELETE) { 245 | // find and delete relevant item 246 | $existing = DataObject::get_one($object->Type, '"ContentID" = \'' . Convert::raw2sql($object->ContentID).'\' AND "MasterNode" = \'' . Convert::raw2sql($object->MasterNode) .'\''); 247 | if ($existing) { 248 | $existing->delete(); 249 | } 250 | } 251 | } 252 | 253 | /** 254 | * Converts an object into a serialised form used for sending over the wire 255 | * 256 | * By default, takes the properties defined directly on it and any has_ones and 257 | * converts to a format readable by the unsyncroObject method 258 | * 259 | * @param DataObject $item 260 | */ 261 | public function syncroObject(DataObject $item, $props = null, $hasOne = null) { 262 | 263 | $properties = array(); 264 | 265 | if (!$props) { 266 | $props = array_keys($item::$db); 267 | } 268 | 269 | foreach ($props as $name) { 270 | $properties[$name] = $item->$name; 271 | } 272 | 273 | // store owner as email address 274 | if ($item->OwnerID) { 275 | $properties['Restrictable_OwnerEmail'] = $item->Owner()->Email; 276 | } 277 | 278 | $has_ones = array(); 279 | if (!$hasOne) { 280 | $hasOne = $item::$has_one; 281 | } 282 | foreach ($hasOne as $name => $type) { 283 | // get the object 284 | $object = $item->getComponent($name); 285 | if ($object && $object->exists()) { 286 | $has_ones[$name] = array('ContentID' => $object->ContentID, 'Type' => $type); 287 | } 288 | } 289 | 290 | $properties['has_one'] = $has_ones; 291 | 292 | return $properties; 293 | } 294 | 295 | /** 296 | * Set properties on an item based on a syncro serialised object 297 | * 298 | * @param stdClass $object 299 | * @param DataObject $into 300 | */ 301 | public function unsyncroObject($object, $item) { 302 | 303 | foreach ($object as $prop => $val) { 304 | if ($prop == 'has_one' || $prop == 'many_many') { 305 | continue; 306 | } 307 | $item->$prop = $val; 308 | } 309 | 310 | // set the owner of the object if it exists 311 | if (isset($object->Restrictable_OwnerEmail)) { 312 | $owner = DataObject::get_one('Member', '"Email" = \'' . Convert::raw2sql($object->Restrictable_OwnerEmail) .'\''); 313 | if ($owner && $owner->exists()) { 314 | $item->OwnerID = $owner->ID; 315 | } 316 | } 317 | 318 | if (isset($object->has_one)) { 319 | foreach ($object->has_one as $name => $contentProp) { 320 | $object = DataObject::get_one($contentProp->Type, '"ContentID" = \'' . Convert::raw2sql($contentProp->ContentID) .'\''); 321 | if ($object && $object->exists()) { 322 | $propName = $name.'ID'; 323 | $item->$propName = $object->ID; 324 | } 325 | } 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /code/tests/TestSyncrotron.php: -------------------------------------------------------------------------------- 1 | logInWithPermission('ADMIN'); 16 | 17 | $json = $this->updateJSON(); 18 | 19 | $data = json_decode($json); 20 | 21 | foreach ($data->response as $item) { 22 | singleton('SyncrotronService')->processUpdatedObject($item); 23 | } 24 | } 25 | 26 | protected function updateJSON() { 27 | $json = <<Title = 'Whatever'; 36 | $obj->write(); 37 | $this->assertTrue($obj->ID > 0); 38 | 39 | $obj->delete(); 40 | 41 | $this->assertTrue($obj->ID == 0); 42 | 43 | $delete = DataObject::get_one('SyncroDelete', '"ContentID" = \'' . $obj->ContentID .'\''); 44 | $this->assertNotNull($delete); 45 | $this->assertTrue($delete->ID > 0); 46 | } 47 | } 48 | 49 | class SyncroTestObject extends DataObject implements TestOnly, Syncroable { 50 | public static $db = array( 51 | 'Title' => 'Varchar', 52 | ); 53 | 54 | public static $extensions = array( 55 | 'SyncroableExtension' 56 | ); 57 | 58 | public function forSyncro() { 59 | $props = singleton('SyncrotronService')->syncroObject($this); 60 | return $props; 61 | } 62 | 63 | public function fromSyncro($properties) { 64 | singleton('SyncrotronService')->unsyncroObject($properties, $this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /code/thirdparty/_manifest_exclude: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyeholt/silverstripe-syncrotron/bb93691757b283a858a3e460d547c697e2054651/code/thirdparty/_manifest_exclude -------------------------------------------------------------------------------- /code/thirdparty/klogger/.gitignore: -------------------------------------------------------------------------------- 1 | src/test.php 2 | test/ 3 | test/* 4 | test.php 5 | -------------------------------------------------------------------------------- /code/thirdparty/klogger/README.markdown: -------------------------------------------------------------------------------- 1 | # KLogger: A Simple Logging Class For PHP 2 | 3 | A project written by Kenny Katzgrau and originally hosted at 4 | [CodeFury.net](http://codefury.net/projects/klogger/). This marks the 5 | development of a newer version of KLogger. 6 | 7 | ## About 8 | 9 | KLogger is an easy-to-use logging class for PHP. It supports standard log levels 10 | like debug, info, warn, error, and fatal. Additionally, it isn't naive about 11 | file permissions (which is expected). It was meant to be a class that you could 12 | quickly include into a project and have working right away. 13 | 14 | The class was written in 2008, but I have since received a number of emails both 15 | saying 'thanks' and asking me to add features. 16 | 17 | This github project will host the development of the next version of KLogger. 18 | The original version of KLogger is tagged as version 0.1, and is available for 19 | download [here](http://github.com/katzgrau/KLogger/downloads). 20 | 21 | ## Basic Usage 22 | 23 | $log = new KLogger('/var/log/'); # Specify the log directory 24 | $log->logInfo('Returned a million search results'); //Prints to the log file 25 | $log->logFatal('Oh dear.'); //Prints to the log file 26 | 27 | ## Goals 28 | 29 | All of KLogger's internal goals have been met (there used to be a list here). 30 | If you have a feature request, send it to katzgrau@gmail.com 31 | 32 | ## Why use KLogger? 33 | 34 | Why not? Just drop it in and go. If it saves you time and does what you need, 35 | go for it! Take a line from the book of our C-code fathers: "`build` upon the 36 | work of others". 37 | 38 | ## Who uses KLogger? 39 | 40 | Klogger has been used in projects at: 41 | 42 | * The University of Iowa 43 | * The University of Laverne 44 | * The New Jersey Institute of Technology 45 | * Middlesex Hospital in NJ 46 | 47 | Additionally, it's been used in numerous projects, both commercial and personal. 48 | 49 | ## Special Thanks 50 | 51 | Special thanks to all contributors, which right now includes two people: 52 | 53 | [Tim Kinnane](http://twitter.com/etherealtim) 54 | [Brian Fenton](http://github.com/fentie) 55 | 56 | ## License 57 | 58 | The MIT License 59 | 60 | Copyright (c) 2008-2010 Kenny Katzgrau 61 | 62 | Permission is hereby granted, free of charge, to any person obtaining a copy 63 | of this software and associated documentation files (the "Software"), to deal 64 | in the Software without restriction, including without limitation the rights 65 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 66 | copies of the Software, and to permit persons to whom the Software is 67 | furnished to do so, subject to the following conditions: 68 | 69 | The above copyright notice and this permission notice shall be included in 70 | all copies or substantial portions of the Software. 71 | 72 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 73 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 74 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 75 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 76 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 77 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 78 | THE SOFTWARE. 79 | -------------------------------------------------------------------------------- /code/thirdparty/klogger/example/example.php: -------------------------------------------------------------------------------- 1 | logInfo('Info Test'); 9 | $log->logNotice('Notice Test'); 10 | $log->logWarn('Warn Test'); 11 | $log->logError('Error Test'); 12 | $log->logFatal('Fatal Test'); 13 | $log->logAlert('Alert Test'); 14 | $log->logCrit('Crit test'); 15 | $log->logEmerg('Emerg Test'); 16 | -------------------------------------------------------------------------------- /code/thirdparty/klogger/src/KLogger.php: -------------------------------------------------------------------------------- 1 | logInfo('Returned a million search results'); //Prints to the log file 11 | * $log->logFatal('Oh dear.'); //Prints to the log file 12 | * $log->logDebug('x = 5'); //Prints nothing due to current severity threshhold 13 | * 14 | * @author Kenny Katzgrau 15 | * @since July 26, 2008 16 | * @link http://codefury.net 17 | * @version 0.1 18 | */ 19 | 20 | /** 21 | * Class documentation 22 | */ 23 | class KLogger 24 | { 25 | /** 26 | * Error severity, from low to high. From BSD syslog RFC, secion 4.1.1 27 | * @link http://www.faqs.org/rfcs/rfc3164.html 28 | */ 29 | const EMERG = 0; // Emergency: system is unusable 30 | const ALERT = 1; // Alert: action must be taken immediately 31 | const CRIT = 2; // Critical: critical conditions 32 | const ERR = 3; // Error: error conditions 33 | const WARN = 4; // Warning: warning conditions 34 | const NOTICE = 5; // Notice: normal but significant condition 35 | const INFO = 6; // Informational: informational messages 36 | const DEBUG = 7; // Debug: debug messages 37 | 38 | //custom logging level 39 | /** 40 | * Log nothing at all 41 | */ 42 | const OFF = 8; 43 | /** 44 | * Alias for CRIT 45 | * @deprecated 46 | */ 47 | const FATAL = 2; 48 | 49 | /** 50 | * Internal status codes 51 | */ 52 | const STATUS_LOG_OPEN = 1; 53 | const STATUS_OPEN_FAILED = 2; 54 | const STATUS_LOG_CLOSED = 3; 55 | 56 | /** 57 | * Current status of the log file 58 | * @var integer 59 | */ 60 | private $_logStatus = self::STATUS_LOG_CLOSED; 61 | /** 62 | * Holds messages generated by the class 63 | * @var array 64 | */ 65 | private $_messageQueue = array(); 66 | /** 67 | * Path to the log file 68 | * @var string 69 | */ 70 | private $_logFilePath = null; 71 | /** 72 | * Current minimum logging threshold 73 | * @var integer 74 | */ 75 | private $_severityThreshold = self::INFO; 76 | /** 77 | * This holds the file handle for this instance's log file 78 | * @var resource 79 | */ 80 | private $_fileHandle = null; 81 | 82 | /** 83 | * Standard messages produced by the class. Can be modified for il8n 84 | * @var array 85 | */ 86 | private $_messages = array( 87 | //'writefail' => 'The file exists, but could not be opened for writing. Check that appropriate permissions have been set.', 88 | 'writefail' => 'The file could not be written to. Check that appropriate permissions have been set.', 89 | 'opensuccess' => 'The log file was opened successfully.', 90 | 'openfail' => 'The file could not be opened. Check permissions.', 91 | ); 92 | 93 | /** 94 | * Default severity of log messages, if not specified 95 | * @var integer 96 | */ 97 | private static $_defaultSeverity = self::DEBUG; 98 | /** 99 | * Valid PHP date() format string for log timestamps 100 | * @var string 101 | */ 102 | private static $_dateFormat = 'Y-m-d G:i:s'; 103 | /** 104 | * Octal notation for default permissions of the log file 105 | * @var integer 106 | */ 107 | private static $_defaultPermissions = 0777; 108 | /** 109 | * Array of KLogger instances, part of Singleton pattern 110 | * @var array 111 | */ 112 | private static $instances = array(); 113 | 114 | /** 115 | * Partially implements the Singleton pattern. Each $logDirectory gets one 116 | * instance. 117 | * 118 | * @param string $logDirectory File path to the logging directory 119 | * @param integer $severity One of the pre-defined severity constants 120 | * @return KLogger 121 | */ 122 | public static function instance($logDirectory = false, $severity = false) 123 | { 124 | if ($severity === false) { 125 | $severity = self::$_defaultSeverity; 126 | } 127 | 128 | if ($logDirectory === false) { 129 | if (count(self::$instances) > 0) { 130 | return current(self::$instances); 131 | } else { 132 | $logDirectory = dirname(__FILE__); 133 | } 134 | } 135 | 136 | if (in_array($logDirectory, self::$instances)) { 137 | return self::$instances[$logDirectory]; 138 | } 139 | 140 | self::$instances[$logDirectory] = new self($logDirectory, $severity); 141 | 142 | return self::$instances[$logDirectory]; 143 | } 144 | 145 | /** 146 | * Class constructor 147 | * 148 | * @param string $logDirectory File path to the logging directory 149 | * @param integer $severity One of the pre-defined severity constants 150 | * @return void 151 | */ 152 | public function __construct($logDirectory, $severity) 153 | { 154 | $logDirectory = rtrim($logDirectory, '\\/'); 155 | 156 | if ($severity === self::OFF) { 157 | return; 158 | } 159 | 160 | $this->_logFilePath = $logDirectory 161 | . DIRECTORY_SEPARATOR 162 | . 'log_' 163 | . date('Y-m-d') 164 | . '.txt'; 165 | 166 | $this->_severityThreshold = $severity; 167 | if (!file_exists($logDirectory)) { 168 | mkdir($logDirectory, self::$_defaultPermissions, true); 169 | } 170 | 171 | if (file_exists($this->_logFilePath) && !is_writable($this->_logFilePath)) { 172 | $this->_logStatus = self::STATUS_OPEN_FAILED; 173 | $this->_messageQueue[] = $this->_messages['writefail']; 174 | return; 175 | } 176 | 177 | if (($this->_fileHandle = fopen($this->_logFilePath, 'a'))) { 178 | $this->_logStatus = self::STATUS_LOG_OPEN; 179 | $this->_messageQueue[] = $this->_messages['opensuccess']; 180 | } else { 181 | $this->_logStatus = self::STATUS_OPEN_FAILED; 182 | $this->_messageQueue[] = $this->_messages['openfail']; 183 | } 184 | } 185 | 186 | /** 187 | * Class destructor 188 | */ 189 | public function __destruct() 190 | { 191 | if ($this->_fileHandle) { 192 | fclose($this->_fileHandle); 193 | } 194 | } 195 | /** 196 | * Writes a $line to the log with a severity level of DEBUG 197 | * 198 | * @param string $line Information to log 199 | * @return void 200 | */ 201 | public function logDebug($line) 202 | { 203 | $this->log($line, self::DEBUG); 204 | } 205 | 206 | /** 207 | * Returns (and removes) the last message from the queue. 208 | * @return string 209 | */ 210 | public function getMessage() 211 | { 212 | return array_pop($this->_messageQueue); 213 | } 214 | 215 | /** 216 | * Returns the entire message queue (leaving it intact) 217 | * @return array 218 | */ 219 | public function getMessages() 220 | { 221 | return $this->_messageQueue; 222 | } 223 | 224 | /** 225 | * Empties the message queue 226 | * @return void 227 | */ 228 | public function clearMessages() 229 | { 230 | $this->_messageQueue = array(); 231 | } 232 | 233 | /** 234 | * Sets the date format used by all instances of KLogger 235 | * 236 | * @param string $dateFormat Valid format string for date() 237 | */ 238 | public static function setDateFormat($dateFormat) 239 | { 240 | self::$_dateFormat = $dateFormat; 241 | } 242 | 243 | /** 244 | * Writes a $line to the log with a severity level of INFO. Any information 245 | * can be used here, or it could be used with E_STRICT errors 246 | * 247 | * @param string $line Information to log 248 | * @return void 249 | */ 250 | public function logInfo($line) 251 | { 252 | $this->log($line, self::INFO); 253 | } 254 | 255 | /** 256 | * Writes a $line to the log with a severity level of NOTICE. Generally 257 | * corresponds to E_STRICT, E_NOTICE, or E_USER_NOTICE errors 258 | * 259 | * @param string $line Information to log 260 | * @return void 261 | */ 262 | public function logNotice($line) 263 | { 264 | $this->log($line, self::NOTICE); 265 | } 266 | 267 | /** 268 | * Writes a $line to the log with a severity level of WARN. Generally 269 | * corresponds to E_WARNING, E_USER_WARNING, E_CORE_WARNING, or 270 | * E_COMPILE_WARNING 271 | * 272 | * @param string $line Information to log 273 | * @return void 274 | */ 275 | public function logWarn($line) 276 | { 277 | $this->log($line, self::WARN); 278 | } 279 | 280 | /** 281 | * Writes a $line to the log with a severity level of ERR. Most likely used 282 | * with E_RECOVERABLE_ERROR 283 | * 284 | * @param string $line Information to log 285 | * @return void 286 | */ 287 | public function logError($line) 288 | { 289 | $this->log($line, self::ERR); 290 | } 291 | 292 | /** 293 | * Writes a $line to the log with a severity level of FATAL. Generally 294 | * corresponds to E_ERROR, E_USER_ERROR, E_CORE_ERROR, or E_COMPILE_ERROR 295 | * 296 | * @param string $line Information to log 297 | * @return void 298 | * @deprecated Use logCrit 299 | */ 300 | public function logFatal($line) 301 | { 302 | $this->log($line, self::FATAL); 303 | } 304 | 305 | /** 306 | * Writes a $line to the log with a severity level of ALERT. 307 | * 308 | * @param string $line Information to log 309 | * @return void 310 | */ 311 | public function logAlert($line) 312 | { 313 | $this->log($line, self::ALERT); 314 | } 315 | 316 | /** 317 | * Writes a $line to the log with a severity level of CRIT. 318 | * 319 | * @param string $line Information to log 320 | * @return void 321 | */ 322 | public function logCrit($line) 323 | { 324 | $this->log($line, self::CRIT); 325 | } 326 | 327 | /** 328 | * Writes a $line to the log with a severity level of EMERG. 329 | * 330 | * @param string $line Information to log 331 | * @return void 332 | */ 333 | public function logEmerg($line) 334 | { 335 | $this->log($line, self::EMERG); 336 | } 337 | 338 | /** 339 | * Writes a $line to the log with the given severity 340 | * 341 | * @param string $line Text to add to the log 342 | * @param integer $severity Severity level of log message (use constants) 343 | */ 344 | public function log($line, $severity) 345 | { 346 | if ($this->_severityThreshold >= $severity) { 347 | $status = $this->_getTimeLine($severity); 348 | $this->writeFreeFormLine("$status $line \n"); 349 | } 350 | } 351 | 352 | /** 353 | * Writes a line to the log without prepending a status or timestamp 354 | * 355 | * @param string $line Line to write to the log 356 | * @return void 357 | */ 358 | public function writeFreeFormLine($line) 359 | { 360 | if ($this->_logStatus == self::STATUS_LOG_OPEN 361 | && $this->_severityThreshold != self::OFF) { 362 | if (fwrite($this->_fileHandle, $line) === false) { 363 | $this->_messageQueue[] = $this->_messages['writefail']; 364 | } 365 | } 366 | } 367 | 368 | private function _getTimeLine($level) 369 | { 370 | $time = date(self::$_dateFormat); 371 | 372 | switch ($level) { 373 | case self::EMERG: 374 | return "$time - EMERG -->"; 375 | case self::ALERT: 376 | return "$time - ALERT -->"; 377 | case self::CRIT: 378 | return "$time - CRIT -->"; 379 | case self::FATAL: # FATAL is an alias of CRIT 380 | return "$time - FATAL -->"; 381 | case self::NOTICE: 382 | return "$time - NOTICE -->"; 383 | case self::INFO: 384 | return "$time - INFO -->"; 385 | case self::WARN: 386 | return "$time - WARN -->"; 387 | case self::DEBUG: 388 | return "$time - DEBUG -->"; 389 | case self::ERR: 390 | return "$time - ERROR -->"; 391 | default: 392 | return "$time - LOG -->"; 393 | } 394 | } 395 | } 396 | --------------------------------------------------------------------------------