Overview

Namespaces

  • Jlab
    • Eloglib

Classes

  • Jlab\Eloglib\Attachment
  • Jlab\Eloglib\FileAttachment
  • Jlab\Eloglib\Logentry
  • Jlab\Eloglib\LogentryUtil
  • Jlab\Eloglib\URLAttachment
  • Jlab\Eloglib\User

Exceptions

  • Jlab\Eloglib\InvalidXMLException
  • Jlab\Eloglib\IOException
  • Jlab\Eloglib\LogentryException
  • Jlab\Eloglib\LogRuntimeException
  • Jlab\Eloglib\ServerException
  • Jlab\Eloglib\UserException
  • Overview
  • Namespace
  • Class
  1: <?php
  2: /**
  3:  * Class Logentry
  4:  */
  5: 
  6: namespace Jlab\Eloglib;
  7: 
  8: use Dotenv\Dotenv;
  9: use \XMLWriter;
 10: 
 11: /**
 12:  * Class Logentry
 13:  *
 14:  * An electronic log book log entry.
 15:  *
 16:  * @package Jlab\Eloglib
 17:  *
 18:  *
 19:  * Significant changes from Logentry class in elog Drupal module:
 20:  *   - $body_type renamed to $bodyType
 21:  *
 22:  */
 23: class Logentry
 24: {
 25: 
 26:     /**
 27:      * @var Dotenv
 28:      */
 29:     protected $config;
 30: 
 31: 
 32:     /**
 33:      * The log number
 34:      *
 35:      * This number is null for a new entry and non-null for an entry retrieved
 36:      * from the logbook server.
 37:      *
 38:      * @var integer
 39:      */
 40:     protected $lognumber;
 41: 
 42:     /**
 43:      * The log entry title.
 44:      * 255 character limit imposed by Drupal database.
 45:      * @var string
 46:      */
 47:     protected $title;
 48: 
 49:     /**
 50:      * The author of the logentry
 51:      * @var \stdClass
 52:      */
 53:     protected $author;
 54: 
 55:     /**
 56:      * The log entry time
 57:      * Needs to be in ISO 8601 date format (ex: 2004-02-12T15:19:21+00:00)
 58:      * @var string
 59:      */
 60:     protected $created;
 61: 
 62:     /**
 63:      * Whether the logentry should be sticky at the top of lists
 64:      * @var integer (0/1 representing boolean)
 65:      */
 66:     protected $sticky;
 67: 
 68:     /**
 69:      * The log entry body
 70:      * @var string
 71:      */
 72:     protected $body;
 73: 
 74: 
 75:     /**
 76:      * Indicates the formatting of the text in $body.
 77:      *
 78:      * Valid values will correspond to text formats defined in Drupal
 79:      *   examples: plain_text, filtered_html, full_html, etc.
 80:      *
 81:      * @var string
 82:      */
 83:     protected $bodyType;
 84: 
 85:     /**
 86:      * The list of logbooks for the entry
 87:      * @var array
 88:      */
 89:     protected $logbooks = array();
 90: 
 91:     /**
 92:      * The list of attachments for the entry
 93:      * @var array
 94:      */
 95:     protected $attachments = array();
 96: 
 97:     /**
 98:      * The list of users credited with making the entry
 99:      * @var array of stdClass {username, firstname, lastname, etc.}
100:      */
101:     protected $entrymakers = array();
102: 
103:     /**
104:      * The list of tags associated with the log entry.
105:      * Must be valid terms from the tags vocabulary
106:      * @var array
107:      */
108:     protected $tags = array();
109: 
110:     /**
111:      * The possible list of opspr events
112:      * @var array
113:      */
114:     protected $opspr_events = array();
115: 
116:     /**
117:      * The object that holds fields of a new-style ProblemReport (PR)
118:      */
119:     protected $pr;
120: 
121:     /**
122:      * The possible downtime fields
123:      * @var array
124:      */
125:     protected $downtime = array();
126: 
127:     /**
128:      * References to external databases
129:      * @var array [type][]=>[id]
130:      */
131:     protected $references = array();
132: 
133:     /**
134:      * Notifications to send
135:      * @var array
136:      */
137:     protected $notifications = array();
138: 
139:     /**
140:      * Comments attached to the Logentry
141:      * @var array of Comments
142:      */
143:     protected $comments = array();
144: 
145:     /**
146:      * Text to log as reason for a new revision
147:      */
148:     protected $revision_reason;
149: 
150: 
151:     /**
152:      * Instantiate a Logentry
153:      *
154:      * For maximum flexibility, the constructor can accept any of
155:      * the following arguments:
156:      * <ol>
157:      *   <li> A DOMDocument or DOMElement object in Logentry.xsd format </li>
158:      *   <li> The name of an XML file in Logentry.xsd format </li>
159:      *   <li> A title and the name of a logbook (e.g. ELOG, TLOG, etc.) </li>
160:      *   <li> A title and an array of logbook names </li>
161:      * </ol>
162:      * @link https://logbooks.jlab.org/schema/Logentry.xsd
163:      * @link https://github.com/JeffersonLab/elog
164:      */
165:     public function __construct()
166:     {
167:         $args = func_get_args();
168:         if (count($args) > 2 or count($args) < 1) {
169:             throw new LogentryException("Invalid arguments");
170:         }
171: 
172:         if (count($args) == 1) {
173: //            if (is_a($args[0], 'DOMDocument')) {
174: //                $this->constructFromDom($args[0]);
175: //            } elseif (is_a($args[0], 'DOMElement')) {
176: //                //Must convert to a DOMDocument in order to use DOMXpath
177: //                $dom = new DOMDocument('1.0', 'UTF-8');
178: //                $dom->appendChild($dom->importNode($args[0], TRUE));
179: //                //echo $dom->saveXML();
180: //                $this->constructFromDom($dom);
181: //            } elseif (is_a($args[0], 'Elog')) {
182: //                $this->constructFromElog($args[0]);
183: //            }
184:         } elseif (count($args) == 2) {
185:             if (is_string($args[0])) {
186:                 $this->constructFromScratch($args[0], $args[1]);
187:             }
188:         }
189: 
190:         $this->setConfig(__DIR__, '.env');
191:     }
192: 
193:     /**
194:      * Minimal initialization private constructor.
195:      *
196:      * Automatically sets the created and author fields based
197:      * on the system clock and os username respectively.
198:      *
199:      * @param $title
200:      * @param $logbooks
201:      */
202:     protected function constructFromScratch($title, $logbooks)
203:     {
204:         $this->setTitle($title);
205:         $this->setLogbooks($logbooks);
206:         $this->setCreated(time());
207:         $this->setDefaultAuthor();
208:     }
209: 
210:     /**
211:      * Sets the title.
212:      *
213:      * The title is limited to max 255 characters.
214:      *
215:      * @param string $title
216:      * @throws LogentryException
217:      */
218:     public function setTitle($title)
219:     {
220:         // Would it be kinder to simply truncate?
221:         if (strlen($title) > 255) {
222:             throw new LogentryException("Title exceeds limit of 255 characters");
223:         }
224:         $this->title = $title;
225:     }
226: 
227:     /**
228:      * Sets the logbook(s) to which entry belongs
229:      *
230:      * @param mixed $logbooks (logbook name or array of logbook names)
231:      */
232:     public function setLogbooks($logbooks)
233:     {
234:         $this->logbooks = array();
235:         is_array($logbooks) ? $bookList = $logbooks : $bookList = array($logbooks);
236:         foreach ($bookList as $bookName) {
237:             $this->addLogbook($bookName);
238:         }
239:     }
240: 
241:     /**
242:      * Adds a logbook to the list of logbooks for the entry.
243:      *
244:      * @param string $logbook
245:      *
246:      * @link https://logbooks.jlab.org/logbooks
247:      */
248:     public function addLogbook($logbook)
249:     {
250:         $key = strtoupper($logbook);
251:         $this->logbooks[$key] = $logbook;
252:     }
253: 
254: 
255:     /**
256:      * Sets the internal timestamp of the entry.
257:      *
258:      * Stores it as string in ISO 8601 date format
259:      *   ex: 2004-02-12T15:19:21+00:00
260:      *
261:      * @param mixed $date unix integer timestamp or string parsable by php strtotime()
262:      */
263:     public function setCreated($date)
264:     {
265:         if (is_numeric($date)) {
266:             $this->created = date('c', $date);
267:         } else {
268:             $this->created = date('c', strtotime($date));
269:         }
270:     }
271: 
272:     /**
273:      * Defaults the author to the user who owns the current CPU process.
274:      */
275:     protected function setDefaultAuthor()
276:     {
277:         $os_user = posix_getpwuid(posix_getuid());
278:         $this->setAuthor($os_user['name']);
279:     }
280: 
281:     /**
282:      * Sets the author.
283:      *
284:      * @param string $username
285:      * @param array $attributes associative array of additional user attributes
286:      *
287:      */
288:     public function setAuthor($username, array $attributes = array())
289:     {
290:         $this->author = new User($username, $attributes);
291:     }
292: 
293:     /**
294:      * Loads configuration from a .env file.
295:      *
296:      * Defaults to the .env file included with the package.
297:      * Throws if required environment variables are not set.
298:      *
299:      * @param string $dir
300:      * @param string $file
301:      * @param bool $overload whether config file should replace existing settings (def: FALSE)
302:      * @throws LogentryException if required environment variables not present
303:      */
304:     function setConfig($dir, $file, $overload = false)
305:     {
306:         try {
307:             $this->config = new Dotenv($dir, $file);
308:             if ($overload) {
309:                 $this->config->overload();
310:             } else {
311:                 $this->config->load();
312:             }
313:             // Throws if any required env variables are missing
314:             $this->config->required(array(
315:                 'LOG_ENTRY_SCHEMA_URL',
316:                 'SUBMIT_URL',
317:                 'ELOGCERT_FILE',
318:                 'DEFAULT_UNIX_QUEUE_PATH',
319:                 'DEFAULT_WINDOWS_QUEUE_PATH',
320:                 'EMAIL_DOMAIN'
321:             ));
322:         } catch (\Exception $e) {
323:             throw new LogentryException($e->getMessage());
324:         }
325:     }
326: 
327:     /**
328:      * Adds an entry maker.
329:      *
330:      * @param string $username
331:      * @param array $attributes associative array of additional user attributes
332:      *
333:      */
334:     public function addEntryMaker($username, array $attributes = array())
335:     {
336:         $Maker = new User($username, $attributes);
337:         $this->entrymakers[$Maker->username] = $Maker;
338:     }
339: 
340:     /**
341:      * Adds an email adress to notify of the logentry
342:      *
343:      * @param $email
344:      */
345:     public function addNotify($email)
346:     {
347:         if (is_string($email)) {
348:             if (!stristr($email, '@')) {
349:                 $email .= getenv('EMAIL_DOMAIN');
350:             }
351:             $addr = strtolower($email);
352:             $this->notifications[$addr] = $email;
353:         }
354:     }
355: 
356:     /**
357:      * Adds a reference to external data
358:      *
359:      * @param string $type (lognumber, atlis, etc.)
360:      * @param integer $ref (numeric elog_id, task_id, etc.)
361:      */
362:     public function addReference($type, $ref)
363:     {
364:         if ($type && $ref) {
365:             $key = strtolower($type);
366:             $this->references[$key][$ref] = $ref;
367:         }
368:     }
369: 
370:     /**
371:      * Adds a tag from the tags vocabulary
372:      *
373:      * @param string $tag
374:      * @link https://logbooks.jlab.org/tags
375:      */
376:     public function addTag($tag)
377:     {
378:         if ($tag) {
379:             $key = strtolower($tag);
380:             $this->tags[$key] = $tag;
381:         }
382:     }
383: 
384:     /**
385:      * Sets the body and its content type.
386:      *
387:      * @param string $text The content to place in the body
388:      * @param string $type Specifies the formatting of text: text|html
389:      */
390:     public function setBody($text, $type = 'text')
391:     {
392:         $this->body = $text;
393:         $this->bodyType = $type;
394:     }
395: 
396:     /**
397:      * sets the lognumber.
398:      *
399:      * The lognumber will generally be set only when a logentry is retrieved from the
400:      * logbook server, not during creation of a new entry.
401:      *
402:      * The server will reject submissions of new entries with non-null lognumber except from
403:      * a small set of privileged users.  Those users would typically specify the lognumber
404:      * for a new entry only in situations such as importing legacy data.
405:      *
406:      * @param integer $num
407:      */
408:     public function setLognumber($num)
409:     {
410:         if (is_numeric($num)) {
411:             $this->lognumber = (int)$num;
412:         }
413:     }
414: 
415:     /**
416:      * Adds an attachment from a file.
417:      *
418:      * Stores it in the object's attachments array as a base64 encoded string.
419:      *
420:      * @param string $filename
421:      * @param string $caption
422:      * @param string $type Specify a mime_type (defaults to autodetect)
423:      * @throws
424:      */
425:     public function addAttachment($filename, $caption = '', $type = '')
426:     {
427:         $this->attachments[] = new FileAttachment($filename, $caption, $type);
428:     }
429: 
430: 
431:     /**
432:      * Adds an attachment as a URL reference.
433:      *
434:      * @param string $url
435:      * @param string $caption
436:      * @param string $type mimeType
437:      * @throws
438:      */
439:     public function addAttachmentURL($url, $caption = '', $type = '')
440:     {
441:         $this->attachments[] = new URLAttachment($url, $caption, $type);
442:     }
443: 
444: 
445:     /**
446:      * Return Logentry object as an XML text string
447:      *
448:      * @param string $name A name to use for the encompassing XML tag.
449:      * @return string
450:      * @todo Comment, PR, Downtime
451:      */
452:     function getXML($name = 'Logentry')
453:     {
454: 
455:         /* Note that calls to xmlWriter::writeElement seem to implicitly encode
456:          * html entitites and so we don't want to call htmlspecialchars ourselves
457:          * because that results in double-encoding and is a problem.
458:          */
459:         $xw = new xmlWriter();
460:         $xw->openMemory();
461:         $xw->setIndent(true);
462:         $xw->startElement($name);
463: 
464:         $this->xmlWriteLognumber($xw);
465:         $this->xmlWriteCreated($xw);
466:         $this->xmlWriteTitle($xw);
467:         $this->xmlWriteAuthor($xw);
468:         $this->xmlWriteLogbooks($xw);
469:         $this->xmlWriteTags($xw);
470:         $this->xmlWriteEntrymakers($xw);
471:         $this->xmlWriteBody($xw);
472:         $this->xmlWriteNotifications($xw);
473:         $this->xmlWriteReferences($xw);
474: 
475:         //Placing attachments at the end make it easier on someone
476:         //who might try and read the xml file in a terminal or editor.
477:         $this->xmlWriteAttachments($xw);
478: 
479: 
480: //
481: 
482: //        if ($n == 'comments' && count($this->comments) > 0) {
483: //            $xw->startElement('Comments');
484: //            //$xw->writeRaw("\n");
485: //            foreach ($var as $comment) {
486: //                if (method_exists($comment, 'getXML')) {
487: //                    $xw->writeRaw($comment->getXML());
488: //                }
489: //            }
490: //            $xw->endElement();
491: //            continue;
492: //        }
493: //        if ($n == 'opspr_events' && count($this->opspr_events) > 0) {
494: //            $xw->startElement('OPSPREvents');
495: //            foreach ($this->opspr_events as $pr_event) {
496: //                $xw->startElement('OPSPREvent');
497: //                foreach (get_object_vars($pr_event) as $mprop => $mval) {
498: //                    $xw->writeElement($mprop, $mval);
499: //                }
500: //                $xw->endElement();
501: //            }
502: //            $xw->endElement();
503: //            continue;
504: //        }
505: //        if ($n == 'downtime' && !empty($this->downtime)) {
506: //            $xw->startElement('Downtime');
507: //            foreach ($this->downtime as $key => $value) {
508: //                $xw->writeElement($key, $value);
509: //            }
510: //            $xw->endElement();
511: //            continue;
512: //        }
513: 
514: //        if ($n == 'pr' && is_a($this->pr, 'ElogPR')) {
515: //            $xw->writeRaw($this->pr->getXML());
516: //        }
517: 
518:         $xw->endElement();
519:         return $xw->outputMemory(true);
520:     }
521: 
522: 
523:     /**
524:      * Write the lognumber to XML.
525:      *
526:      * @param \xmlWriter $xw
527:      */
528:     protected function xmlWriteLognumber(\xmlWriter $xw)
529:     {
530:         if ($this->lognumber){
531:             $xw->writeElement('lognumber', $this->lognumber);
532:         }
533:     }
534: 
535:     /**
536:      * Write the title to XML.
537:      *
538:      * @param \xmlWriter $xw
539:      */
540:     protected function xmlWriteTitle(\xmlWriter $xw)
541:     {
542:         $xw->writeElement('title', $this->title);
543:     }
544: 
545:     /**
546:      * Write the created timestamp to XML.
547:      * @param \xmlWriter $xw
548:      */
549:     protected function xmlWriteCreated(\xmlWriter $xw)
550:     {
551:         $xw->writeElement('created', $this->created);
552:     }
553: 
554: 
555:     /**
556:      * Write the author timestamp to XML.
557:      *
558:      * @param \xmlWriter $xw
559:      */
560:     protected function xmlWriteAuthor(\xmlWriter $xw)
561:     {
562:         $xw->writeRaw($this->author->getXML('Author'));
563:     }
564: 
565:     /**
566:      * Write the logbook names to XML.
567:      *
568:      * @param \xmlWriter $xw
569:      */
570:     protected function xmlWriteLogbooks(\xmlWriter $xw)
571:     {
572:         $xw->startElement('Logbooks');
573:         foreach ($this->logbooks as $logbook) {
574:             $xw->writeElement('logbook', $logbook);
575:         }
576:         $xw->endElement();
577:     }
578: 
579:     /**
580:      * Write the tag names to XML.
581:      *
582:      * @param \xmlWriter $xw
583:      */
584:     protected function xmlWriteTags(\xmlWriter $xw)
585:     {
586:         if (count($this->tags) > 0) {
587:             $xw->startElement('Tags');
588:             foreach ($this->tags as $tag) {
589:                 $xw->writeElement('tag', $tag);
590:             }
591:             $xw->endElement();
592:         }
593:     }
594: 
595:     /**
596:      * Write the entrymakers to XML.
597:      *
598:      * @param \xmlWriter $xw
599:      */
600:     protected function xmlWriteEntrymakers(\xmlWriter $xw)
601:     {
602:         if (count($this->entrymakers) > 0) {
603:             $xw->startElement('Entrymakers');
604:             foreach ($this->entrymakers as $maker) {
605:                 $xw->writeRaw($maker->getXML('Entrymaker'));
606:             }
607:             $xw->endElement();
608:         }
609:     }
610: 
611:     /**
612:      * Write the body content and type to XML.
613:      *
614:      * @param \xmlWriter $xw
615:      */
616:     protected function xmlWriteBody(\xmlWriter $xw)
617:     {
618:         if ($this->body != '') {
619:             $xw->startElement('body');
620:             switch ($this->bodyType) {
621:                 case 'elog_text' :          //Really was HTML
622:                 case 'text/html' :
623:                 case 'html' :
624:                 case 'trusted_html' :
625:                 case 'full_html' :
626:                     $body_type = 'html';
627:                     break;
628:                 default :
629:                     $body_type = 'text';
630:             }
631:             $xw->writeAttribute('type', $body_type);
632:             $xw->writeCData($this->body);
633:             $xw->endElement();
634:         }
635:     }
636: 
637:     /**
638:      * Write the email notification recipients to XML.
639:      *
640:      * @param \xmlWriter $xw
641:      */
642:     protected function xmlWriteNotifications(\xmlWriter $xw)
643:     {
644:         if (count($this->notifications) > 0) {
645:             $xw->startElement('Notifications');
646:             foreach ($this->notifications as $email) {
647:                 $xw->writeElement('email', $email);
648:             }
649:             $xw->endElement();
650:         }
651:     }
652: 
653:     /**
654:      * Write the external data references to XML.
655:      *
656:      * @param \xmlWriter $xw
657:      */
658:     protected function xmlWriteReferences(\xmlWriter $xw)
659:     {
660:         if (count($this->references) > 0) {
661:             $xw->startElement('References');
662:             foreach ($this->references as $type => $ref) {
663:                 foreach ($ref as $r) {
664:                     $xw->startElement('reference');
665:                     $xw->writeAttribute('type', $type);
666:                     $xw->text($r);
667:                     $xw->endElement();
668:                 }
669:             }
670:             $xw->endElement();
671:         }
672:     }
673: 
674:     /**
675:      * Write the attachments to XML.
676:      *
677:      * @param XMLWriter $xw
678:      */
679:     protected function xmlWriteAttachments(\xmlWriter $xw){
680:         if (count($this->attachments) > 0) {
681:             $xw->startElement('Attachments');
682:             foreach ($this->attachments as $attachment) {
683:                 $xw->writeRaw($attachment->getXML('Attachment'));
684:             }
685:             $xw->endElement();
686:         }
687:     }
688: 
689:     /**
690:      * Submit the log item directly to the server and return the assigned log number, but
691:      * fall back to use the queue mechanism as plan B.
692:      *
693:      * If the log number returned is zero it indicates the submission was queued instead of being accepted by the server.
694:      * You can use the whyQueued method to obtain the ServerException encountered if any while attempting
695:      * to submit directly to the server.
696:      *
697:      * @return int The log number, zero means queued
698:      * @throws LogentryException
699:      */
700:     public function submit(){
701:         // First attempt is to submit the entry and get back immediate
702:         // confirmation in the form of the log number assigned.
703:         try {
704:             return $this->submitNow();
705:         } catch ( ServerException $e){
706:             error_log($e->getMessage());
707:         }
708: 
709:         // Then fall back and attempt queueing instead.
710:         // Pass false to second parameter to prevent second redundant
711:         // XML validation attempt.
712:         $queueFile = LogentryUtil::saveToQueue($this, false);
713:         if (file_exists($queueFile)){
714:             return 0;
715:         }
716:     }
717: 
718:     /**
719:      * Submit the log item using the queue mechanism only.
720:      *
721:      * @return mixed the filename that was queued or false for failure.
722:      * @throws IOException if unable to write queue file
723:      * @throws InvalidXMLException
724:      */
725:     public function queue(){
726:         $queueFile = LogentryUtil::saveToQueue($this);
727:         if (file_exists($queueFile)){
728:             return $queueFile;
729:         }
730:         return false;
731:     }
732: 
733:     /**
734:      * Submit the log item using only the direct submission method with no queue fallback.
735:      *
736:      * If an error occurs during submission then an Exception will be thrown
737:      * instead of falling back to the queue method.
738:      *
739:      * @return integer The log number
740:      * @throws
741:      */
742:     public function submitNow(){
743:         if (! LogentryUtil::isValidEntry($this)){
744:             throw new InvalidXMLException("Schema validation of the entry fails: \n" . LogentryUtil::validationErrors());
745:         }
746:         return LogentryUtil::saveToServer($this);
747:     }
748: 
749:     /**
750:      * Return the ServerException which prevented direct submission to the server on the most recent attempt, or null if none.
751:      *
752:      * This method allows access to the exception which is masked when the submit method is called and returns
753:      * with a zero value indicating the submission was queued.
754:      */
755:     public function whyQueued(){
756:         //TODO implement method
757:     }
758: 
759: 
760: 
761:     /**
762:      * Magic method controls access to class properties.
763:      *
764:      * @param string $var
765:      * @return mixed
766:      */
767:     function __get($var)
768:     {
769:         if (property_exists($this, $var)) {
770:             $getterFunction = 'get' . ucfirst($var);
771:             if (method_exists($this, $getterFunction)) {
772:                 return $this->$getterFunction();
773:             } else {
774:                 return $this->$var;
775:             }
776:         }
777: 
778:         $trace = debug_backtrace();
779:         trigger_error(
780:             'Undefined property via __get(): ' . $var .
781:             ' in ' . $trace[0]['file'] .
782:             ' on line ' . $trace[0]['line'], E_USER_NOTICE);
783:         return null;
784:     }
785: 
786: 
787: }
API documentation generated by ApiGen