       1: <?php
       2:
       3: /**
       4:  *
       5:  * The master controller class for the Alpha Framework.
       6:  *
       7:  * @package alpha::controller
       8:  * @since 1.0
       9:  * @author John Collins <dev@alphaframework.org>
      10:  * @version $Id: AlphaController.inc 1548 2012-07-29 17:07:07Z alphadevx $
      11:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
      12:  * @copyright Copyright (c) 2012, John Collins (founder of Alpha Framework).
      13:  * All rights reserved.
      14:  *
      15:  * <pre>
      16:  * Redistribution and use in source and binary forms, with or
      17:  * without modification, are permitted provided that the
      18:  * following conditions are met:
      19:  *
      20:  * * Redistributions of source code must retain the above
      21:  *   copyright notice, this list of conditions and the
      22:  *   following disclaimer.
      23:  * * Redistributions in binary form must reproduce the above
      24:  *   copyright notice, this list of conditions and the
      25:  *   following disclaimer in the documentation and/or other
      26:  *   materials provided with the distribution.
      27:  * * Neither the name of the Alpha Framework nor the names
      28:  *   of its contributors may be used to endorse or promote
      29:  *   products derived from this software without specific
      30:  *   prior written permission.
      31:  *
      32:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
      33:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
      34:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
      35:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      36:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
      37:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      38:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
      39:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      40:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      41:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
      42:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
      43:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
      44:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      45:  * </pre>
      46:  *
      47:  */
      48: abstract class AlphaController {
      49:     /**
      50:      * The name of the controller
      51:      *
      52:      * @var string
      53:      * @since 1.0
      54:      */
      55:     protected $name;
      56:
      57:     /**
      58:      * Used to set access privileages for the controller to the name of the rights group
      59:      * allowed to access it.  'Public' by default.
      60:      *
      61:      * @var string
      62:      * @since 1.0
      63:      */
      64:     protected $visibility = 'Public';
      65:
      66:     /**
      67:      * Optionally, a BO may be set for the default validation form handling code to load in the displayPageHead()
      68:      * method.  The definition of this BO class will need to be included in the child controller.
      69:      *
      70:      * @var AlphaDAO
      71:      * @since 1.0
      72:      */
      73:     protected $BO = null;
      74:
      75:     /**
      76:      * Used to determine if the controller is part of a unit of work sequence
      77:      * (either empty or the name of the unit).
      78:      *
      79:      * @var string
      80:      * @since 1.0
      81:      */
      82:     protected $unitOfWork;
      83:
      84:     /**
      85:      * Stores the start time of a unit of work transaction.
      86:      *
      87:      * @var Timestamp
      88:      * @since 1.0
      89:      */
      90:     protected $unitStartTime;
      91:
      92:     /**
      93:      * Stores the end time of a unit of work transaction.
      94:      *
      95:      * @var Timestamp.
      96:      * @since 1.0
      97:      */
      98:     protected $unitEndTime;
      99:
     100:     /**
     101:      * Stores the maximum allowed time duration (in seconds) of the unit of work.
     102:      *
     103:      * @var Integer
     104:      * @since 1.0
     105:      */
     106:     protected $unitMAXDuration;
     107:
     108:     /**
     109:      * The name of the first controller that is used in this unit of work.
     110:      *
     111:      * @var string
     112:      * @since 1.0
     113:      */
     114:     protected $firstJob;
     115:
     116:     /**
     117:      * The name of the next controller that is used in this unit of work.
     118:      *
     119:      * @var string
     120:      * @since 1.0
     121:      */
     122:     protected $nextJob;
     123:
     124:     /**
     125:      * The name of the previous controller that is used in this unit of work.
     126:      *
     127:      * @var string
     128:      * @since 1.0
     129:      */
     130:     protected $previousJob;
     131:
     132:     /**
     133:      * The name of the last controller that is used in this unit of work.
     134:      *
     135:      * @var string
     136:      * @since 1.0
     137:      */
     138:     protected $lastJob;
     139:
     140:     /**
     141:      * An array for storing dirty objects in a session (i.e. persistent business
     142:      * objects that have not been updated in the database yet).
     143:      *
     144:      * @var array
     145:      * @since 1.0
     146:      */
     147:     protected $dirtyObjects = array();
     148:
     149:     /**
     150:      * An array for storing new objects in a session (transient business objects that
     151:      * have no OID yet).
     152:      *
     153:      * @var array
     154:      * @since 1.0
     155:      */
     156:     protected $newObjects = array();
     157:
     158:     /**
     159:      * The title to be displayed on the controller page
     160:      *
     161:      * @var string
     162:      * @since 1.0
     163:      */
     164:     protected $title;
     165:
     166:     /**
     167:      * Meta keywords for the controller page, generally populated from tags
     168:      *
     169:      * @var string
     170:      * @since 1.0
     171:      */
     172:     protected $keywords;
     173:
     174:     /**
     175:      * Meta description for the controller page.
     176:      *
     177:      * @var string
     178:      * @since 1.0
     179:      */
     180:     protected $description;
     181:
     182:     /**
     183:      * Used to set status update messages to display to the user (messages stored between requests
     184:      * in _SESSION).  Useful for when you want to display a message to a user after POSTing a request,
     185:      * or when moving from one page to the next.
     186:      *
     187:      * @var string
     188:      * @since 1.0
     189:      */
     190:     protected $statusMessage;
     191:
     192:     /**
     193:      * Trace logger
     194:      *
     195:      * @var Logger
     196:      * @since 1.0
     197:      */
     198:     private static $logger = null;
     199:
     200:     /**
     201:      * Constructor for the AlphaController that starts a new session if required, and handles
     202:      * the population of new/dirty objects from the session when available.  Accepts the name
     203:      * of the rights group that has access to this controller, 'Public' by default.
     204:      *
     205:      * @param string $visibility The name of the rights group that can access this controller.
     206:      * @since 1.0
     207:      */
     208:     public function __construct($visibility='Public') {
     209:         self::$logger = new Logger('AlphaController');
     210:         self::$logger->debug('>>__construct(visibility=['.$visibility.'])');
     211:
     212:         global $config;
     213:
     214:         // kick off new session, or reuse existing one
     215:         if(!isset($_SESSION))
     216:             session_start();
     217:
     218:         // set the access rights to the group name indicated
     219:         $this->visibility = $visibility;
     220:
     221:         // check the current user's rights on access to the page controller
     222:         if(!$this->checkRights()) {
     223:             $this->accessError();
     224:             // no more execution should take place
     225:             exit;
     226:         }
     227:
     228:         // if configured to do so, force redirect to the front controller
     229:         if($config->get('app.force.front.controller') && basename($_SERVER['PHP_SELF']) != 'index.php') {
     230:             // set the correct HTTP header for the response
     231:                 header('HTTP/1.1 301 Moved Permanently');
     232:
     233:                 if(empty($_SERVER['QUERY_STRING'])) {
     234:                     header('Location: '.$config->get('app.url').'?act='.get_class($this));
     235:                     self::$logger->debug('<<__construct');
     236:                     exit;
     237:                 }else{
     238:                     header('Location: '.$config->get('app.url').'?act='.get_class($this).'&'.$_SERVER['QUERY_STRING']);
     239:                     self::$logger->debug('<<__construct');
     240:                     exit;
     241:                 }
     242:
     243:                 // we're done here
     244:                 self::$logger->debug('<<__construct');
     245:                 return;
     246:         }
     247:
     248:         $this->unitStartTime = new Timestamp(date("Y-m-d H:i:s"));
     249:         $this->unitEndTime = new Timestamp();
     250:         $this->unitMAXDuration = new Integer();
     251:
     252:         // uses controller class name as the job name
     253:         if($this->name == '')
     254:             $this->setName(get_class($this));
     255:
     256:         if(isset($_SESSION['unitOfWork']) && is_array($_SESSION['unitOfWork']))
     257:             $this->setUnitOfWork($_SESSION['unitOfWork']);
     258:
     259:         if(isset($_SESSION['dirtyObjects']) && is_array($_SESSION['dirtyObjects']))
     260:             $this->dirtyObjects = $_SESSION['dirtyObjects'];
     261:
     262:         if(isset($_SESSION['newObjects']) && is_array($_SESSION['newObjects']))
     263:             $this->newObjects = $_SESSION['newObjects'];
     264:
     265:         if(isset($_SESSION['statusMessage']))
     266:             $this->setStatusMessage($_SESSION['statusMessage']);
     267:
     268:         self::$logger->debug('<<__construct');
     269:      }
     270:
     271:      /**
     272:       * Get the BO for this controller (if any).
     273:       *
     274:       * @return mixed
     275:       * @since 1.0
     276:       */
     277:      public function getBO() {
     278:         self::$logger->debug('>>getBO()');
     279:         self::$logger->debug('<<getBO ['.var_export($this->BO, true).']');
     280:         return $this->BO;
     281:      }
     282:
     283:      /**
     284:       * Setter for the BO for this controller.
     285:       *
     286:       * @param AlphaDAO $BO
     287:       * @since 1.0
     288:       */
     289:      public function setBO($BO) {
     290:         self::$logger->debug('>>setBO(BO=['.var_export($BO, true).'])');
     291:         $this->BO = $BO;
     292:
     293:         // if the BO has tags, use these as the meta keywords for this controller
     294:         if($this->BO->isTagged()) {
     295:             $tags = $this->BO->getPropObject('tags')->getRelatedObjects();
     296:
     297:             $keywords = '';
     298:
     299:             if(count($tags) > 0) {
     300:                 foreach($tags as $tag)
     301:                     $keywords .= ','.$tag->get('content');
     302:             }
     303:
     304:             $this->setKeywords(substr($keywords, 1));
     305:         }
     306:
     307:         self::$logger->debug('<<setBO');
     308:      }
     309:
     310:      /**
     311:       * Get the name of the unit of work job.
     312:       *
     313:       * @return string
     314:       * @since 1.0
     315:       */
     316:      public function getName() {
     317:         self::$logger->debug('>>getName()');
     318:         self::$logger->debug('<<getName ['.$this->name.']');
     319:         return $this->name;
     320:      }
     321:
     322:      /**
     323:       * Setter for the unit of work job name.
     324:       *
     325:       * @param string $name
     326:       * @since 1.0
     327:       */
     328:      public function setName($name) {
     329:         self::$logger->debug('>>setName(name=['.$name.'])');
     330:         $this->name = $name;
     331:         self::$logger->debug('<<setName');
     332:      }
     333:
     334:      /**
     335:       * Get the name of the rights group that has access to this controller.
     336:       *
     337:       * @return string
     338:       * @since 1.0
     339:       */
     340:      public function getVisibility() {
     341:         self::$logger->debug('>>getVisibility()');
     342:         self::$logger->debug('<<getVisibility ['.$this->visibility.']');
     343:         return $this->visibility;
     344:      }
     345:
     346:      /**
     347:       * Setter for the name of the rights group that has access to this controller.
     348:       *
     349:       * @param string $visibility
     350:       * @since 1.0
     351:       */
     352:      public function setVisibility($visibility) {
     353:         self::$logger->debug('>>setVisibility(visibility=['.$visibility.'])');
     354:         $this->visibility = $visibility;
     355:         self::$logger->debug('<<setVisibility');
     356:      }
     357:
     358:      /**
     359:       * Gets the name of the first job in this unit of work.
     360:       *
     361:       * @return string
     362:       * @since 1.0
     363:       */
     364:      public function getFirstJob() {
     365:         self::$logger->debug('>>getFirstJob()');
     366:         self::$logger->debug('<<getFirstJob ['.$this->firstJob.']');
     367:         return $this->firstJob;
     368:      }
     369:
     370:      /**
     371:       * Gets the name of the next job in this unit of work
     372:       *
     373:       * @return string
     374:       * @since 1.0
     375:       */
     376:      public function getNextJob() {
     377:         self::$logger->debug('>>getNextJob()');
     378:         self::$logger->debug('<<getNextJob ['.$this->nextJob.']');
     379:         return $this->nextJob;
     380:      }
     381:
     382:      /**
     383:       * Gets the name of the previous job in this unit of work
     384:       *
     385:       * @return string
     386:       * @since 1.0
     387:       */
     388:      public function getPreviousJob() {
     389:         self::$logger->debug('>>getPreviousJob()');
     390:         self::$logger->debug('<<getPreviousJob ['.$this->previousJob.']');
     391:         return $this->previousJob;
     392:      }
     393:
     394:      /**
     395:       * Gets the name of the last job in this unit of work.
     396:       *
     397:       * @return string
     398:       * @since 1.0
     399:       */
     400:      public function getLastJob() {
     401:         self::$logger->debug('>>getLastJob()');
     402:         self::$logger->debug('<<getLastJob ['.$this->lastJob.']');
     403:         return $this->lastJob;
     404:      }
     405:
     406:      /**
     407:       * Sets the name of the controller job sequence to the values in the supplied
     408:       * array (and stores the array in the session).
     409:       *
     410:       * @param array $jobs The names of the controllers in this unit of work sequence.
     411:       * @throws IllegalArguementException
     412:       * @since 1.0
     413:       */
     414:      public function setUnitOfWork($jobs) {
     415:         self::$logger->debug('>>setUnitOfWork(jobs=['.var_export($jobs, true).'])');
     416:
     417:         if(method_exists($this, 'before_setUnitOfWork_callback'))
     418:             $this->before_setUnitOfWork_callback();
     419:
     420:         if(!is_array($jobs)) {
     421:             throw new IllegalArguementException('Bad $jobs array ['.var_export($jobs, true).'] passed to setUnitOfWork method!');
     422:             self::$logger->debug('<<setUnitOfWork');
     423:             return;
     424:         }
     425:
     426:         // validate that each controller name in the array actually exists
     427:         foreach($jobs as $job) {
     428:             if(!AlphaController::checkControllerDefExists($job))
     429:                 throw new IllegalArguementException('The controller name ['.$job.'] provided in the jobs array is not defined anywhere!');
     430:         }
     431:
     432:         // clear out any previous unit of work from the session
     433:         $_SESSION['unitOfWork'] = null;
     434:         $this->dirtyObjects = array();
     435:         $this->newObjects = array();
     436:
     437:         $numOfJobs = count($jobs);
     438:
     439:         for($i=0; $i<$numOfJobs; $i++) {
     440:             // the first job in the sequence
     441:             if($i==0) {
     442:                 $this->firstJob = $jobs[$i];
     443:                 self::$logger->debug('First job ['.$this->firstJob.']');
     444:             }
     445:             // found the current job
     446:             if($this->name == $jobs[$i]) {
     447:                 if(isset($jobs[$i-1])) {
     448:                     // set the previous job if it exists
     449:                     $this->previousJob = $jobs[$i-1];
     450:                     self::$logger->debug('Previous job ['.$this->previousJob.']');
     451:                 }
     452:                 if(isset($jobs[$i+1])) {
     453:                     // set the next job if it exists
     454:                     $this->nextJob = $jobs[$i+1];
     455:                     self::$logger->debug('Next job ['.$this->nextJob.']');
     456:                 }
     457:             }
     458:             // the last job in the sequence
     459:             if($i==($numOfJobs-1)) {
     460:                 $this->lastJob = $jobs[$i];
     461:             }
     462:         }
     463:
     464:         $_SESSION['unitOfWork'] = $jobs;
     465:
     466:         if(method_exists($this, 'after_setUnitOfWork_callback'))
     467:             $this->after_setUnitOfWork_callback();
     468:
     469:         self::$logger->debug('<<setUnitOfWork');
     470:      }
     471:
     472:      /**
     473:       * Getter for the unit start time.
     474:       *
     475:       * @return Timestamp
     476:       * @since 1.0
     477:       */
     478:      public function getStartTime() {
     479:         self::$logger->debug('>>getStartTime()');
     480:         self::$logger->debug('<<getStartTime ['.$this->unitStartTime.']');
     481:         return $this->unitStartTime;
     482:      }
     483:
     484:      /**
     485:       * Setter for the unit start time (value will be stored in the session as key unitStartTime).
     486:       *
     487:       * @param integer $year
     488:       * @param integer $month
     489:       * @param integer $day
     490:       * @param integer $hour
     491:       * @param integer $minute
     492:       * @param integer $second
     493:       * @since 1.0
     494:       */
     495:      public function setUnitStartTime($year, $month, $day, $hour, $minute, $second) {
     496:         self::$logger->debug('>>setUnitStartTime(year=['.$year.'], month=['.$month.'], day=['.$day.'], hour=['.$hour.'], minute=['.$minute.'],
     497:             second=['.$second.'])');
     498:
     499:         $this->unitStartTime->setTimestampValue($year, $month, $day, $hour, $minute, $second);
     500:         $_SESSION['unitStartTime'] = $this->unitStartTime->getValue();
     501:
     502:         self::$logger->debug('<<setUnitStartTime');
     503:      }
     504:
     505:      /**
     506:       * Getter for the unit end time.
     507:       *
     508:       * @return Timestamp
     509:       * @since 1.0
     510:       */
     511:      public function getEndTime() {
     512:         self::$logger->debug('>>getEndTime()');
     513:         self::$logger->debug('<<getEndTime ['.$this->unitEndTime.']');
     514:         return $this->unitEndTime;
     515:      }
     516:
     517:      /**
     518:       * Setter for the unit end time (value will be stored in the session as key unitEndTime).
     519:       *
     520:       * @param integer $year
     521:       * @param integer $month
     522:       * @param integer $day
     523:       * @param integer $hour
     524:       * @param integer $minute
     525:       * @param integer $second
     526:       * @since 1.0
     527:       */
     528:      public function setUnitEndTime($year, $month, $day, $hour, $minute, $second) {
     529:         self::$logger->debug('>>setUnitEndTime(year=['.$year.'], month=['.$month.'], day=['.$day.'], hour=['.$hour.'], minute=['.$minute.'],
     530:          second=['.$second.'])');
     531:
     532:         $this->unitEndTime->setTimestampValue($year, $month, $day, $hour, $minute, $second);
     533:         $_SESSION['unitEndTime'] = $this->unitEndTime->getValue();
     534:
     535:         self::$logger->debug('<<setUnitEndTime');
     536:      }
     537:
     538:      /**
     539:       * Getter for the unit of work MAX duration.
     540:       *
     541:       * @return Integer
     542:       * @since 1.0
     543:       */
     544:      public function getMAXDuration() {
     545:         self::$logger->debug('>>getMAXDuration()');
     546:         self::$logger->debug('<<getMAXDuration ['.$this->unitMAXDuration.']');
     547:         return $this->unitMAXDuration;
     548:      }
     549:
     550:      /**
     551:       * Setter for the unit MAX duration.
     552:       *
     553:       * @param integer $duration The desired duration in seconds.
     554:       * @since 1.0
     555:       */
     556:      public function setUnitMAXDuration($duration) {
     557:         self::$logger->debug('>>setUnitMAXDuration(duration=['.$duration.'])');
     558:         $this->unitMAXDuration->setValue($duration);
     559:         self::$logger->debug('<<setUnitMAXDuration');
     560:      }
     561:
     562:      /**
     563:       * Calculates and returns the unit of work current duration in seconds.
     564:       *
     565:       * @return integer
     566:       * @since 1.0
     567:       */
     568:      public function getUnitDuration() {
     569:         self::$logger->debug('>>getUnitDuration()');
     570:
     571:         $intStartTime = mktime(
     572:             $this->unitStartTime->getHour(),
     573:             $this->unitStartTime->getMinute(),
     574:             $this->unitStartTime->getSecond(),
     575:             $this->unitStartTime->getMonth(),
     576:             $this->unitStartTime->getDay(),
     577:             $this->unitStartTime->getYear()
     578:             );
     579:
     580:         $intEndTime = mktime(
     581:             $this->unitEndTime->getHour(),
     582:             $this->unitEndTime->getMinute(),
     583:             $this->unitEndTime->getSecond(),
     584:             $this->unitEndTime->getMonth(),
     585:             $this->unitEndTime->getDay(),
     586:             $this->unitEndTime->getYear()
     587:             );
     588:
     589:         self::$logger->debug('<<getUnitDuration ['.$intEndTime-$intStartTime.']');
     590:         return $intEndTime-$intStartTime;
     591:      }
     592:
     593:      /**
     594:       * Adds the supplied business object to the dirtyObjects array in the session.
     595:       *
     596:       * @param AlphaDAO $object
     597:       * @since 1.0
     598:       */
     599:      public function markDirty($object) {
     600:         self::$logger->debug('>>markDirty(object=['.var_export($object, true).'])');
     601:
     602:         if(method_exists($this, 'before_markDirty_callback'))
     603:             $this->before_markDirty_callback();
     604:
     605:         $this->dirtyObjects[count($this->dirtyObjects)] = $object;
     606:
     607:         $_SESSION['dirtyObjects'] = $this->dirtyObjects;
     608:
     609:         if(method_exists($this, 'after_markDirty_callback'))
     610:             $this->after_markDirty_callback();
     611:
     612:         self::$logger->debug('<<markDirty');
     613:      }
     614:
     615:      /**
     616:       * Getter for the dirty objects array.
     617:       *
     618:       * @return array
     619:       * @since 1.0
     620:       */
     621:      public function getDirtyObjects() {
     622:         self::$logger->debug('>>getDirtyObjects()');
     623:         self::$logger->debug('<<getDirtyObjects ['.var_export($this->dirtyObjects, true).']');
     624:         return $this->dirtyObjects;
     625:      }
     626:
     627:      /**
     628:       * Adds a newly created business object to the newObjects array in the session.
     629:       *
     630:       * @param AlphaDAO $object
     631:       * @since 1.0
     632:       */
     633:      public function markNew($object) {
     634:         self::$logger->debug('>>markNew(object=['.var_export($object, true).'])');
     635:
     636:         if(method_exists($this, 'before_markNew_callback'))
     637:             $this->before_markNew_callback();
     638:
     639:         $this->newObjects[count($this->newObjects)] = $object;
     640:
     641:         $_SESSION['newObjects'] = $this->newObjects;
     642:
     643:         if(method_exists($this, 'after_markNew_callback'))
     644:             $this->after_markNew_callback();
     645:
     646:         self::$logger->debug('<<markNew');
     647:      }
     648:
     649:      /**
     650:       * Getter for the new objects array.
     651:       *
     652:       * @return array
     653:       * @since 1.0
     654:       */
     655:      public function getNewObjects() {
     656:         self::$logger->debug('>>getNewObjects()');
     657:         self::$logger->debug('<<getNewObjects ['.var_export($this->newObjects, true).']');
     658:         return $this->newObjects;
     659:      }
     660:
     661:     /**
     662:      * Commits (saves) all of the new and modified (dirty) objects in the unit of work to the database.
     663:      *
     664:      * @throws FailedUnitCommitException
     665:      * @since 1.0
     666:      */
     667:     public function commit() {
     668:         self::$logger->debug('>>commit()');
     669:
     670:         if(method_exists($this, 'before_commit_callback'))
     671:             $this->before_commit_callback();
     672:
     673:         AlphaDAO::begin();
     674:
     675:         $newObjects = $this->getNewObjects();
     676:
     677:         $count = count($newObjects);
     678:
     679:         for ($i = 0; $i < $count; $i++) {
     680:             try {
     681:                 $newObjects[$i]->save();
     682:             }catch (FailedSaveException $e) {
     683:                 throw new FailedUnitCommitException($e->getMessage());
     684:                 self::$logger->error('Failed to save new object of type ['.get_class($newObjects[$i]).'], aborting...');
     685:                 $this->abort();
     686:                 return;
     687:             }catch (LockingException $e) {
     688:                 throw new FailedUnitCommitException($e->getMessage());
     689:                 self::$logger->error('Failed to save new object of type ['.get_class($newObjects[$i]).'], aborting...');
     690:                 $this->abort();
     691:                 return;
     692:             }
     693:         }
     694:
     695:         $dirtyObjects = $this->getDirtyObjects();
     696:
     697:         $count = count($dirtyObjects);
     698:
     699:         for ($i = 0; $i < $count; $i++) {
     700:             try {
     701:                 $dirtyObjects[$i]->save();
     702:             }catch (FailedSaveException $e) {
     703:                 throw new FailedUnitCommitException($e->getMessage());
     704:                 self::$logger->error('Failed to save OID ['.$dirtyObjects[$i]->getID().'] of type ['.get_class($dirtyObjects[$i]).'], aborting...');
     705:                 $this->abort();
     706:                 return;
     707:             }catch (LockingException $e) {
     708:                 throw new FailedUnitCommitException($e->getMessage());
     709:                 self::$logger->error('Failed to save OID ['.$dirtyObjects[$i]->getID().'] of type ['.get_class($dirtyObjects[$i]).'], aborting...');
     710:                 $this->abort();
     711:                 return;
     712:             }
     713:         }
     714:
     715:         try{
     716:             AlphaDAO::commit();
     717:
     718:             $this->clearUnitOfWorkAttributes();
     719:
     720:             if(method_exists($this, 'after_commit_callback'))
     721:                 $this->after_commit_callback();
     722:
     723:             self::$logger->debug('<<commit');
     724:         }catch(FailedSaveException $e) {
     725:             throw new FailedUnitCommitException('Failed to commit the transaction, error is ['.$e->getMessage().']');
     726:             self::$logger->debug('<<commit');
     727:         }
     728:     }
     729:
     730:     /**
     731:      * Method to clearup a cancelled unit of work.
     732:      *
     733:      * @throws AlphaException
     734:      * @since 1.0
     735:      */
     736:     public function abort() {
     737:         self::$logger->debug('>>abort()');
     738:
     739:         if(method_exists($this, 'before_abort_callback'))
     740:             $this->before_abort_callback();
     741:
     742:         try{
     743:             AlphaDAO::rollback();
     744:
     745:             $this->clearUnitOfWorkAttributes();
     746:
     747:             if(method_exists($this, 'after_abort_callback'))
     748:                 $this->after_abort_callback();
     749:
     750:             self::$logger->debug('<<abort');
     751:         }catch(AlphaException $e) {
     752:             throw new AlphaException('Failed to rollback the transaction, error is ['.$e->getMessage().']');
     753:             self::$logger->debug('<<abort');
     754:         }
     755:     }
     756:
     757:     /**
     758:      * Clears the session and object attributes related to unit of work sessions
     759:      */
     760:     private function clearUnitOfWorkAttributes() {
     761:         $_SESSION['unitOfWork'] = null;
     762:         $this->unitOfWork = null;
     763:         $_SESSION['dirtyObjects'] = null;
     764:         $this->dirtyObjects = array();
     765:         $_SESSION['newObjects'] = null;
     766:         $this->newObjects = array();
     767:     }
     768:
     769:     /**
     770:      * Getter for the page title.
     771:      *
     772:      * @return string
     773:      * @since 1.0
     774:      */
     775:     public function getTitle() {
     776:         self::$logger->debug('>>getTitle()');
     777:         self::$logger->debug('<<getTitle ['.$this->title.']');
     778:         return $this->title;
     779:     }
     780:
     781:     /**
     782:      * Setter for the page title.
     783:      *
     784:      * @param string $title
     785:      * @since 1.0
     786:      */
     787:     public function setTitle($title) {
     788:         self::$logger->debug('>>setTitle(title=['.$title.'])');
     789:         self::$logger->debug('<<setTitle');
     790:         $this->title = $title;
     791:     }
     792:
     793:     /**
     794:      * Getter for the page description.
     795:      *
     796:      * @return string
     797:      * @since 1.0
     798:      */
     799:     public function getDescription() {
     800:         self::$logger->debug('>>getDescription()');
     801:         self::$logger->debug('<<getDescription ['.$this->description.']');
     802:         return $this->description;
     803:     }
     804:
     805:     /**
     806:      * Setter for the page description.
     807:      *
     808:      * @param string $description
     809:      * @since 1.0
     810:      */
     811:     public function setDescription($description) {
     812:         self::$logger->debug('>>setDescription(description=['.$description.'])');
     813:         self::$logger->debug('<<setDescription');
     814:         $this->description = $description;
     815:     }
     816:
     817:     /**
     818:      * Getter for the page keywords.
     819:      *
     820:      * @return string
     821:      * @since 1.0
     822:      */
     823:     public function getKeywords() {
     824:         self::$logger->debug('>>getKeywords()');
     825:         self::$logger->debug('<<getKeywords ['.$this->keywords.']');
     826:         return $this->keywords;
     827:     }
     828:
     829:     /**
     830:      * Setter for the page keywords, should pass a comma-seperated list as a string.
     831:      *
     832:      * @param string $keywords
     833:      * @since 1.0
     834:      */
     835:     public function setKeywords($keywords) {
     836:         self::$logger->debug('>>setKeywords(keywords=['.$keywords.'])');
     837:         self::$logger->debug('<<setKeywords');
     838:         $this->keywords = $keywords;
     839:     }
     840:
     841:     /**
     842:      * Method to display an access error for trespassing users.  HTTP response header code will be 403.
     843:      *
     844:      * @since 1.0
     845:      */
     846:     public function accessError() {
     847:         self::$logger->debug('>>accessError()');
     848:
     849:         if(method_exists($this, 'before_accessError_callback'))
     850:             $this->before_accessError_callback();
     851:
     852:         global $config;
     853:
     854:         if(isset($_SESSION['currentUser']))
     855:             self::$logger->warn('The user ['.$_SESSION['currentUser']->get('email').'] attempted to access the resource ['.$_SERVER['REQUEST_URI'].'] but was denied due to insufficient rights');
     856:         else
     857:             self::$logger->warn('An unknown user attempted to access the resource ['.$_SERVER['REQUEST_URI'].'] but was denied due to insufficient rights');
     858:
     859:         header('HTTP/1.1 403 Forbidden');
     860:         $front = new FrontController();
     861:         echo AlphaView::renderErrorPage(403, 'You do not have the correct access rights to view this page.  If you have not logged in yet, try going back to the home page and logging in from there.');
     862:
     863:         if(method_exists($this, 'after_accessError_callback'))
     864:             $this->after_accessError_callback();
     865:
     866:         self::$logger->debug('<<accessError');
     867:     }
     868:
     869:     /**
     870:      * Checks the user rights of the currently logged-in person against the page
     871:      * visibility set for this controller.  Will return false if the user has
     872:      * not got the correct rights.
     873:      *
     874:      * @return boolean
     875:      * @since 1.0
     876:      */
     877:     public function checkRights() {
     878:         self::$logger->debug('>>checkRights()');
     879:
     880:         if(method_exists($this, 'before_checkRights_callback'))
     881:             $this->before_checkRights_callback();
     882:
     883:         // firstly if the page is Public then there is no issue
     884:         if ($this->getVisibility() == 'Public') {
     885:             if(method_exists($this, 'after_checkRights_callback'))
     886:                 $this->after_checkRights_callback();
     887:
     888:             self::$logger->debug('<<checkRights [true]');
     889:             return true;
     890:         }else{
     891:             // the person is logged in?
     892:             if (isset($_SESSION['currentUser'])) {
     893:                 // checking for admins (can access everything)
     894:                 if ($_SESSION['currentUser']->inGroup('Admin')) {
     895:                     if(method_exists($this, 'after_checkRights_callback'))
     896:                         $this->after_checkRights_callback();
     897:
     898:                     self::$logger->debug('<<checkRights [true]');
     899:                     return true;
     900:                 } elseif ($_SESSION['currentUser']->inGroup($this->getVisibility())) {
     901:                     if(method_exists($this, 'after_checkRights_callback'))
     902:                         $this->after_checkRights_callback();
     903:
     904:                     self::$logger->debug('<<checkRights [true]');
     905:                     return true;
     906:                 // the person is editing their own profile which is allowed
     907:                 } elseif (get_class($this->BO) == 'PersonObject' && $_SESSION['currentUser']->getDisplayName() == $this->BO->getDisplayName()) {
     908:                     if(method_exists($this, 'after_checkRights_callback'))
     909:                         $this->after_checkRights_callback();
     910:
     911:                     self::$logger->debug('<<checkRights [true]');
     912:                     return true;
     913:                 }else{
     914:                     self::$logger->debug('<<checkRights [false]');
     915:                     return false;
     916:                 }
     917:             }else{ // the person is NOT logged in
     918:                 self::$logger->debug('<<checkRights [false]');
     919:                 return false;
     920:             }
     921:         }
     922:     }
     923:
     924:     /**
     925:      * Method to check the validity of the two hidden form security
     926:      * fields which aim to ensure that a post to the controller is being sent from
     927:      * the same server that is hosting it.
     928:      *
     929:      * @return boolean
     930:      * @since 1.0
     931:      */
     932:     public static function checkSecurityFields() {
     933:         if(self::$logger == null)
     934:             self::$logger = new Logger('AlphaController');
     935:         self::$logger->debug('>>checkSecurityFields()');
     936:
     937:         // the server hostname + today's date
     938:         $var1 = md5($_SERVER['HTTP_HOST'].date("Ymd"));
     939:         // the server's IP plus $var1
     940:         $var2 = md5($_SERVER['REMOTE_ADDR'].$var1);
     941:
     942:         if(empty($_REQUEST['var1']) || empty($_REQUEST['var2'])) {
     943:             self::$logger->warn('The required var1/var2 params where not provided on the HTTP request');
     944:             self::$logger->debug('<<checkSecurityFields [false]');
     945:             return false;
     946:         }
     947:
     948:         if ($var1 == $_REQUEST['var1'] && $var2 == $_REQUEST['var2']) {
     949:             self::$logger->debug('<<checkSecurityFields [true]');
     950:             return true;
     951:         }else{
     952:             /*
     953:              * Here we are implementing a "grace period" of one hour if the time is < 1:00AM, we will accept
     954:              * a match for yesterday's date in the security fields
     955:              *
     956:              */
     957:
     958:             // the server hostname + today's date less 1 hour (i.e. yesterday where time is < 1:00AM)
     959:             $var1 = md5($_SERVER['HTTP_HOST'].date("Ymd", (time()-3600)));
     960:             // the server's IP plus $var1
     961:             $var2 = md5($_SERVER['REMOTE_ADDR'].$var1);
     962:
     963:             if ($var1 == $_REQUEST['var1'] && $var2 == $_REQUEST['var2']) {
     964:                 self::$logger->debug('<<checkSecurityFields [true]');
     965:                 return true;
     966:             }else{
     967:                 self::$logger->warn('The var1/var2 params provided are invalid, values: var1=['.$_REQUEST['var1'].'] var2=['.$_REQUEST['var2'].']');
     968:                 self::$logger->debug('<<checkSecurityFields [false]');
     969:                 return false;
     970:             }
     971:         }
     972:     }
     973:
     974:     /**
     975:      * Generates the two security fields to prevent remote form processing.
     976:      *
     977:      * @return array An array containing the two fields
     978:      * @since 1.0
     979:      */
     980:     public static function generateSecurityFields() {
     981:         if(self::$logger == null)
     982:             self::$logger = new Logger('AlphaController');
     983:         self::$logger->debug('>>generateSecurityFields()');
     984:
     985:         // the server hostname + today's date
     986:         $var1 = md5($_SERVER['HTTP_HOST'].date("Ymd"));
     987:         // the server's IP plus $var1
     988:         $var2 = md5($_SERVER['REMOTE_ADDR'].$var1);
     989:
     990:         self::$logger->debug('<<generateSecurityFields [array('.$var1.', '.$var2.')]');
     991:         return array($var1, $var2);
     992:     }
     993:
     994:     /**
     995:      * Returns the name of a custom controller if one is found, otherwise returns null.
     996:      *
     997:      * @param string $BOName The classname of the business object
     998:      * @param string $mode The mode of the controller (create, view, edit)
     999:      * @return string
    1000:      * @since 1.0
    1001:      */
    1002:     public static function getCustomControllerName($BOName, $mode) {
    1003:         if(self::$logger == null)
    1004:             self::$logger = new Logger('AlphaController');
    1005:         self::$logger->debug('>>getCustomControllerName(BOName=['.$BOName.'], mode=['.$mode.'])');
    1006:
    1007:         global $config;
    1008:
    1009:         // strip the Object part from the class name
    1010:         $BOName = substr($BOName, 0, strpos($BOName, 'Object'));
    1011:         // uppercase the first letter of each word, e.g. create cart becomes Create Cart
    1012:         $controllerName = ucwords($mode.' '.$BOName);
    1013:         // remove spaces
    1014:         $controllerName = str_replace(' ', '', $controllerName);
    1015:
    1016:         self::$logger->debug('Custom controller name is ['.$controllerName.']');
    1017:
    1018:         if (file_exists($config->get('app.root').'controller/'.$controllerName.'.php')) {
    1019:             self::$logger->debug('<<getCustomControllerName');
    1020:             return $controllerName;
    1021:         }elseif (file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php')) {
    1022:             self::$logger->debug('<<getCustomControllerName');
    1023:             return $controllerName;
    1024:         }else{
    1025:             self::$logger->debug('<<getCustomControllerName');
    1026:             return null;
    1027:         }
    1028:     }
    1029:
    1030:     /**
    1031:      * Does a HTTP redirect to a custom controller if one is found.
    1032:      *
    1033:      * @param string $BOName The classname of the business object
    1034:      * @param string $mode The mode of the controller (create, view, edit)
    1035:      * @throws FileNotFoundException
    1036:      * @since 1.0
    1037:      */
    1038:     protected function loadCustomController($BOName, $mode) {
    1039:         self::$logger->debug('>>loadCustomController(BOName=['.$BOName.'], mode=['.$mode.'])');
    1040:
    1041:         global $config;
    1042:
    1043:         // strip the Object part from the class name
    1044:         $BOName = substr($BOName, 0, strpos($BOName, 'Object'));
    1045:         // uppercase the first letter of each word, e.g. create cart becomes Create Cart
    1046:         $controllerName = ucwords($mode.' '.$BOName);
    1047:         // remove spaces
    1048:         $controllerName = str_replace(' ', '', $controllerName);
    1049:
    1050:         self::$logger->debug('Custom controller name is ['.$controllerName.']');
    1051:
    1052:         // just making sure that we are not already using the custom controller
    1053:         if(get_class($this) != $controllerName) {
    1054:             if (file_exists($config->get('app.root').'controller/'.$controllerName.'.php')) {
    1055:                 self::$logger->debug('Custom controller found, redirecting...');
    1056:                 // handle secure URLs
    1057:                 if(isset($_GET['tk'])) {
    1058:                     $params = FrontController::decodeQueryParams($_GET['tk']);
    1059:                     $params = preg_replace('/act=.*\&/', 'act='.$controllerName.'&', $params);
    1060:                     self::$logger->debug('Params are ['.$params.']');
    1061:
    1062:                     $url = FrontController::generateSecureURL($params);
    1063:                     self::$logger->debug('Redirecting to ['.$url.']');
    1064:                     header('Location: '.$url);
    1065:                     self::$logger->debug('<<loadCustomController');
    1066:                     exit;
    1067:                 }else{
    1068:                     $url = $config->get('app.url').'controller/'.$controllerName.'.php?'.$_SERVER['QUERY_STRING'];
    1069:                     self::$logger->debug('Redirecting to ['.$url.']');
    1070:                     header('Location: '.$url);
    1071:                     self::$logger->debug('<<loadCustomController');
    1072:                     exit;
    1073:                 }
    1074:             }elseif (file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php')) {
    1075:                 self::$logger->debug('Custom controller found, redirecting...');
    1076:                 // handle secure URLs
    1077:                 if(self::checkIfAccessingFromSecureURL()) {
    1078:                     if(isset($_GET['tk'])) {
    1079:                         $params = FrontController::decodeQueryParams($_GET['tk']);
    1080:                     }else{
    1081:                         $start = strpos($_SERVER['REQUEST_URI'], '/tk/')+3;
    1082:                         $end = strlen($_SERVER['REQUEST_URI']);
    1083:                         $tk = substr($_SERVER['REQUEST_URI'], $start+1, $end-($start+1));
    1084:                         $params = FrontController::decodeQueryParams($tk);
    1085:                     }
    1086:
    1087:                     $params = preg_replace('/act=.*\&/', 'act='.$controllerName.'&', $params);
    1088:                     self::$logger->debug('Params are ['.$params.']');
    1089:
    1090:                     $url = FrontController::generateSecureURL($params);
    1091:                     self::$logger->debug('Redirecting to ['.$url.']');
    1092:                     header('Location: '.$url);
    1093:                     self::$logger->debug('<<loadCustomController');
    1094:                     exit;
    1095:                 }else{
    1096:                     $url = $config->get('app.url').'alpha/controller/'.$controllerName.'.php?'.$_SERVER['QUERY_STRING'];
    1097:                     self::$logger->debug('Redirecting to ['.$url.']');
    1098:                     header('Location: '.$url);
    1099:                     self::$logger->debug('<<loadCustomController');
    1100:                     exit;
    1101:                 }
    1102:             }else{
    1103:                 // throw an exception if we have gotten this far and no custom controller was found
    1104:                 throw new FileNotFoundException('The controller ['.$controllerName.'] could not be loaded as it does not exist');
    1105:             }
    1106:         }
    1107:
    1108:         self::$logger->debug('<<loadCustomController');
    1109:     }
    1110:
    1111:     /**
    1112:      * Set the status message in the _SESSION to the value provided.
    1113:      *
    1114:      * @param string $message
    1115:      * @since 1.0
    1116:      */
    1117:     public function setStatusMessage($message) {
    1118:         $this->statusMessage = $message;
    1119:         $_SESSION['statusMessage'] = $message;
    1120:     }
    1121:
    1122:     /**
    1123:      * Gets the current status message for this controller.  Note that by getting the current
    1124:      * status message, you clear out the value stored in _SESSION so this method can only be used
    1125:      * to get the status message once for display purposes.
    1126:      *
    1127:      * @return string
    1128:      * @since 1.0
    1129:      */
    1130:     public function getStatusMessage() {
    1131:         $_SESSION['statusMessage'] = null;
    1132:         return $this->statusMessage;
    1133:     }
    1134:
    1135:     /**
    1136:      * Checks that the definition for the controller classname provided exists.  Will also return true
    1137:      * if you pass "/" for the root of the web application.
    1138:      *
    1139:      * @param string $controllerName
    1140:      * @return boolean
    1141:      * @since 1.0
    1142:      */
    1143:     public static function checkControllerDefExists($controllerName) {
    1144:         if(self::$logger == null)
    1145:             self::$logger = new Logger('AlphaController');
    1146:         self::$logger->debug('>>checkControllerDefExists(controllerName=['.$controllerName.'])');
    1147:
    1148:         global $config;
    1149:
    1150:         $exists = false;
    1151:
    1152:         if($controllerName == '/')
    1153:             $exists = true;
    1154:         if(file_exists($config->get('app.root').'controller/'.$controllerName.'.php'))
    1155:             $exists = true;
    1156:         if(file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php'))
    1157:             $exists = true;
    1158:
    1159:         self::$logger->debug('<<checkControllerDefExists ['.$exists.']');
    1160:         return $exists;
    1161:     }
    1162:
    1163:     /**
    1164:      * Loads the definition for the controller classname provided.
    1165:      *
    1166:      * @param string $controllerName
    1167:      * @throws IllegalArguementException
    1168:      * @since 1.0
    1169:      */
    1170:     public static function loadControllerDef($controllerName) {
    1171:         if(self::$logger == null)
    1172:             self::$logger = new Logger('AlphaController');
    1173:         self::$logger->debug('>>loadControllerDef(controllerName=['.$controllerName.'])');
    1174:
    1175:         global $config;
    1176:
    1177:         if(file_exists($config->get('app.root').'controller/'.$controllerName.'.php'))
    1178:             require_once $config->get('app.root').'controller/'.$controllerName.'.php';
    1179:         elseif(file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php'))
    1180:             require_once $config->get('app.root').'alpha/controller/'.$controllerName.'.php';
    1181:         else
    1182:             throw new IllegalArguementException('The class ['.$controllerName.'] is not defined anywhere!');
    1183:
    1184:         self::$logger->debug('<<loadControllerDef');
    1185:     }
    1186:
    1187:     /**
    1188:      * Static function for determining if the current request URL is a secure one (has a tk string or not)
    1189:      *
    1190:      * @return boolean True if the current URL contains a tk value, false otherwise
    1191:      * @since 1.0
    1192:      */
    1193:     public static function checkIfAccessingFromSecureURL() {
    1194:         if (isset($_GET['tk']) || strpos($_SERVER['REQUEST_URI'], '/tk/') !== false)
    1195:             return true;
    1196:         else
    1197:             return false;
    1198:     }
    1199: }
    1200:
    1201: ?>
    1202: