       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 1745 2014-03-29 15:19:05Z alphadevx $
      11:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
      12:  * @copyright Copyright (c) 2014, 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:             $url = parse_url($config->get('app.url'));
     217:             $hostname = $url['host'];
     218:             session_set_cookie_params(0, '/', $hostname, false, true);
     219:             session_start();
     220:         }
     221:
     222:         // set the access rights to the group name indicated
     223:         $this->visibility = $visibility;
     224:
     225:         // check the current user's rights on access to the page controller
     226:         if(!$this->checkRights()) {
     227:             $this->accessError();
     228:             // no more execution should take place
     229:             exit;
     230:         }
     231:
     232:         // if configured to do so, force redirect to the front controller
     233:         if($config->get('app.force.front.controller') && basename($_SERVER['PHP_SELF']) != 'index.php') {
     234:             // set the correct HTTP header for the response
     235:                 header('HTTP/1.1 301 Moved Permanently');
     236:
     237:                 if(empty($_SERVER['QUERY_STRING'])) {
     238:                     header('Location: '.$config->get('app.url').'?act='.get_class($this));
     239:                     self::$logger->debug('<<__construct');
     240:                     exit;
     241:                 }else{
     242:                     header('Location: '.$config->get('app.url').'?act='.get_class($this).'&'.$_SERVER['QUERY_STRING']);
     243:                     self::$logger->debug('<<__construct');
     244:                     exit;
     245:                 }
     246:
     247:                 // we're done here
     248:                 self::$logger->debug('<<__construct');
     249:                 return;
     250:         }
     251:
     252:         $this->unitStartTime = new Timestamp(date("Y-m-d H:i:s"));
     253:         $this->unitEndTime = new Timestamp();
     254:         $this->unitMAXDuration = new Integer();
     255:
     256:         // uses controller class name as the job name
     257:         if($this->name == '')
     258:             $this->setName(get_class($this));
     259:
     260:         if(isset($_SESSION['unitOfWork']) && is_array($_SESSION['unitOfWork']))
     261:             $this->setUnitOfWork($_SESSION['unitOfWork']);
     262:
     263:         if(isset($_SESSION['dirtyObjects']) && is_array($_SESSION['dirtyObjects']))
     264:             $this->dirtyObjects = $_SESSION['dirtyObjects'];
     265:
     266:         if(isset($_SESSION['newObjects']) && is_array($_SESSION['newObjects']))
     267:             $this->newObjects = $_SESSION['newObjects'];
     268:
     269:         if(isset($_SESSION['statusMessage']))
     270:             $this->setStatusMessage($_SESSION['statusMessage']);
     271:
     272:         if($config->get('security.encrypt.http.fieldnames') && !empty($_POST))
     273:             $this->decryptFieldNames();
     274:
     275:         self::$logger->debug('<<__construct');
     276:     }
     277:
     278:     /**
     279:      * Get the BO for this controller (if any).
     280:      *
     281:      * @return mixed
     282:      * @since 1.0
     283:      */
     284:     public function getBO() {
     285:         self::$logger->debug('>>getBO()');
     286:         self::$logger->debug('<<getBO ['.var_export($this->BO, true).']');
     287:         return $this->BO;
     288:     }
     289:
     290:     /**
     291:      * Setter for the BO for this controller.
     292:      *
     293:      * @param AlphaDAO $BO
     294:      * @since 1.0
     295:      */
     296:     public function setBO($BO) {
     297:         self::$logger->debug('>>setBO(BO=['.var_export($BO, true).'])');
     298:         $this->BO = $BO;
     299:
     300:         // if the BO has tags, use these as the meta keywords for this controller
     301:         if($this->BO->isTagged()) {
     302:             $tags = $this->BO->getPropObject('tags')->getRelatedObjects();
     303:
     304:             $keywords = '';
     305:
     306:             if(count($tags) > 0) {
     307:                 foreach($tags as $tag)
     308:                     $keywords .= ','.$tag->get('content');
     309:             }
     310:
     311:             $this->setKeywords(mb_substr($keywords, 1));
     312:         }
     313:
     314:         self::$logger->debug('<<setBO');
     315:     }
     316:
     317:     /**
     318:      * Get the name of the unit of work job.
     319:      *
     320:      * @return string
     321:      * @since 1.0
     322:      */
     323:     public function getName() {
     324:         self::$logger->debug('>>getName()');
     325:         self::$logger->debug('<<getName ['.$this->name.']');
     326:         return $this->name;
     327:     }
     328:
     329:     /**
     330:      * Setter for the unit of work job name.
     331:      *
     332:      * @param string $name
     333:      * @since 1.0
     334:      */
     335:     public function setName($name) {
     336:         self::$logger->debug('>>setName(name=['.$name.'])');
     337:         $this->name = $name;
     338:         self::$logger->debug('<<setName');
     339:      }
     340:
     341:     /**
     342:      * Get the name of the rights group that has access to this controller.
     343:      *
     344:      * @return string
     345:      * @since 1.0
     346:      */
     347:     public function getVisibility() {
     348:         self::$logger->debug('>>getVisibility()');
     349:         self::$logger->debug('<<getVisibility ['.$this->visibility.']');
     350:         return $this->visibility;
     351:     }
     352:
     353:     /**
     354:      * Setter for the name of the rights group that has access to this controller.
     355:      *
     356:      * @param string $visibility
     357:      * @since 1.0
     358:      */
     359:     public function setVisibility($visibility) {
     360:         self::$logger->debug('>>setVisibility(visibility=['.$visibility.'])');
     361:         $this->visibility = $visibility;
     362:         self::$logger->debug('<<setVisibility');
     363:     }
     364:
     365:     /**
     366:      * Gets the name of the first job in this unit of work.
     367:      *
     368:      * @return string
     369:      * @since 1.0
     370:      */
     371:     public function getFirstJob() {
     372:         self::$logger->debug('>>getFirstJob()');
     373:         self::$logger->debug('<<getFirstJob ['.$this->firstJob.']');
     374:         return $this->firstJob;
     375:     }
     376:
     377:     /**
     378:      * Gets the name of the next job in this unit of work
     379:      *
     380:      * @return string
     381:      * @since 1.0
     382:      */
     383:     public function getNextJob() {
     384:         self::$logger->debug('>>getNextJob()');
     385:         self::$logger->debug('<<getNextJob ['.$this->nextJob.']');
     386:         return $this->nextJob;
     387:     }
     388:
     389:     /**
     390:      * Gets the name of the previous job in this unit of work
     391:      *
     392:      * @return string
     393:      * @since 1.0
     394:      */
     395:     public function getPreviousJob() {
     396:         self::$logger->debug('>>getPreviousJob()');
     397:         self::$logger->debug('<<getPreviousJob ['.$this->previousJob.']');
     398:         return $this->previousJob;
     399:     }
     400:
     401:     /**
     402:      * Gets the name of the last job in this unit of work.
     403:      *
     404:      * @return string
     405:      * @since 1.0
     406:      */
     407:     public function getLastJob() {
     408:         self::$logger->debug('>>getLastJob()');
     409:         self::$logger->debug('<<getLastJob ['.$this->lastJob.']');
     410:         return $this->lastJob;
     411:     }
     412:
     413:     /**
     414:      * Sets the name of the controller job sequence to the values in the supplied
     415:      * array (and stores the array in the session).
     416:      *
     417:      * @param array $jobs The names of the controllers in this unit of work sequence.
     418:      * @throws IllegalArguementException
     419:      * @since 1.0
     420:      */
     421:     public function setUnitOfWork($jobs) {
     422:         self::$logger->debug('>>setUnitOfWork(jobs=['.var_export($jobs, true).'])');
     423:
     424:         if(method_exists($this, 'before_setUnitOfWork_callback'))
     425:             $this->before_setUnitOfWork_callback();
     426:
     427:         if(!is_array($jobs)) {
     428:             throw new IllegalArguementException('Bad $jobs array ['.var_export($jobs, true).'] passed to setUnitOfWork method!');
     429:             self::$logger->debug('<<setUnitOfWork');
     430:             return;
     431:         }
     432:
     433:         // validate that each controller name in the array actually exists
     434:         foreach($jobs as $job) {
     435:             if(!AlphaController::checkControllerDefExists($job))
     436:                 throw new IllegalArguementException('The controller name ['.$job.'] provided in the jobs array is not defined anywhere!');
     437:         }
     438:
     439:         // clear out any previous unit of work from the session
     440:         $_SESSION['unitOfWork'] = null;
     441:         $this->dirtyObjects = array();
     442:         $this->newObjects = array();
     443:
     444:         $numOfJobs = count($jobs);
     445:
     446:         for($i=0; $i<$numOfJobs; $i++) {
     447:             // the first job in the sequence
     448:             if($i==0) {
     449:                 $this->firstJob = $jobs[$i];
     450:                 self::$logger->debug('First job ['.$this->firstJob.']');
     451:             }
     452:             // found the current job
     453:             if($this->name == $jobs[$i]) {
     454:                 if(isset($jobs[$i-1])) {
     455:                     // set the previous job if it exists
     456:                     $this->previousJob = $jobs[$i-1];
     457:                     self::$logger->debug('Previous job ['.$this->previousJob.']');
     458:                 }
     459:                 if(isset($jobs[$i+1])) {
     460:                     // set the next job if it exists
     461:                     $this->nextJob = $jobs[$i+1];
     462:                     self::$logger->debug('Next job ['.$this->nextJob.']');
     463:                 }
     464:             }
     465:             // the last job in the sequence
     466:             if($i==($numOfJobs-1)) {
     467:                 $this->lastJob = $jobs[$i];
     468:             }
     469:         }
     470:
     471:         $_SESSION['unitOfWork'] = $jobs;
     472:
     473:         if(method_exists($this, 'after_setUnitOfWork_callback'))
     474:             $this->after_setUnitOfWork_callback();
     475:
     476:         self::$logger->debug('<<setUnitOfWork');
     477:     }
     478:
     479:     /**
     480:      * Getter for the unit start time.
     481:      *
     482:      * @return Timestamp
     483:      * @since 1.0
     484:      */
     485:     public function getStartTime() {
     486:         self::$logger->debug('>>getStartTime()');
     487:         self::$logger->debug('<<getStartTime ['.$this->unitStartTime.']');
     488:         return $this->unitStartTime;
     489:     }
     490:
     491:     /**
     492:      * Setter for the unit start time (value will be stored in the session as key unitStartTime).
     493:      *
     494:      * @param integer $year
     495:      * @param integer $month
     496:      * @param integer $day
     497:      * @param integer $hour
     498:      * @param integer $minute
     499:      * @param integer $second
     500:      * @since 1.0
     501:      */
     502:     public function setUnitStartTime($year, $month, $day, $hour, $minute, $second) {
     503:         self::$logger->debug('>>setUnitStartTime(year=['.$year.'], month=['.$month.'], day=['.$day.'], hour=['.$hour.'], minute=['.$minute.'],
     504:             second=['.$second.'])');
     505:
     506:         $this->unitStartTime->setTimestampValue($year, $month, $day, $hour, $minute, $second);
     507:         $_SESSION['unitStartTime'] = $this->unitStartTime->getValue();
     508:
     509:         self::$logger->debug('<<setUnitStartTime');
     510:     }
     511:
     512:     /**
     513:      * Getter for the unit end time.
     514:      *
     515:      * @return Timestamp
     516:      * @since 1.0
     517:      */
     518:     public function getEndTime() {
     519:         self::$logger->debug('>>getEndTime()');
     520:         self::$logger->debug('<<getEndTime ['.$this->unitEndTime.']');
     521:         return $this->unitEndTime;
     522:     }
     523:
     524:     /**
     525:      * Setter for the unit end time (value will be stored in the session as key unitEndTime).
     526:      *
     527:      * @param integer $year
     528:      * @param integer $month
     529:      * @param integer $day
     530:      * @param integer $hour
     531:      * @param integer $minute
     532:      * @param integer $second
     533:      * @since 1.0
     534:      */
     535:     public function setUnitEndTime($year, $month, $day, $hour, $minute, $second) {
     536:         self::$logger->debug('>>setUnitEndTime(year=['.$year.'], month=['.$month.'], day=['.$day.'], hour=['.$hour.'], minute=['.$minute.'],
     537:          second=['.$second.'])');
     538:
     539:         $this->unitEndTime->setTimestampValue($year, $month, $day, $hour, $minute, $second);
     540:         $_SESSION['unitEndTime'] = $this->unitEndTime->getValue();
     541:
     542:         self::$logger->debug('<<setUnitEndTime');
     543:     }
     544:
     545:     /**
     546:      * Getter for the unit of work MAX duration.
     547:      *
     548:      * @return Integer
     549:      * @since 1.0
     550:      */
     551:     public function getMAXDuration() {
     552:         self::$logger->debug('>>getMAXDuration()');
     553:         self::$logger->debug('<<getMAXDuration ['.$this->unitMAXDuration.']');
     554:         return $this->unitMAXDuration;
     555:     }
     556:
     557:     /**
     558:      * Setter for the unit MAX duration.
     559:      *
     560:      * @param integer $duration The desired duration in seconds.
     561:      * @since 1.0
     562:      */
     563:     public function setUnitMAXDuration($duration) {
     564:         self::$logger->debug('>>setUnitMAXDuration(duration=['.$duration.'])');
     565:         $this->unitMAXDuration->setValue($duration);
     566:         self::$logger->debug('<<setUnitMAXDuration');
     567:     }
     568:
     569:     /**
     570:      * Calculates and returns the unit of work current duration in seconds.
     571:      *
     572:      * @return integer
     573:      * @since 1.0
     574:      */
     575:     public function getUnitDuration() {
     576:         self::$logger->debug('>>getUnitDuration()');
     577:
     578:         $intStartTime = mktime(
     579:             $this->unitStartTime->getHour(),
     580:             $this->unitStartTime->getMinute(),
     581:             $this->unitStartTime->getSecond(),
     582:             $this->unitStartTime->getMonth(),
     583:             $this->unitStartTime->getDay(),
     584:             $this->unitStartTime->getYear()
     585:             );
     586:
     587:         $intEndTime = mktime(
     588:             $this->unitEndTime->getHour(),
     589:             $this->unitEndTime->getMinute(),
     590:             $this->unitEndTime->getSecond(),
     591:             $this->unitEndTime->getMonth(),
     592:             $this->unitEndTime->getDay(),
     593:             $this->unitEndTime->getYear()
     594:             );
     595:
     596:         self::$logger->debug('<<getUnitDuration ['.$intEndTime-$intStartTime.']');
     597:         return $intEndTime-$intStartTime;
     598:      }
     599:
     600:     /**
     601:      * Adds the supplied business object to the dirtyObjects array in the session.
     602:      *
     603:      * @param AlphaDAO $object
     604:      * @since 1.0
     605:      */
     606:     public function markDirty($object) {
     607:         self::$logger->debug('>>markDirty(object=['.var_export($object, true).'])');
     608:
     609:         if(method_exists($this, 'before_markDirty_callback'))
     610:             $this->before_markDirty_callback();
     611:
     612:         $this->dirtyObjects[count($this->dirtyObjects)] = $object;
     613:
     614:         $_SESSION['dirtyObjects'] = $this->dirtyObjects;
     615:
     616:         if(method_exists($this, 'after_markDirty_callback'))
     617:             $this->after_markDirty_callback();
     618:
     619:         self::$logger->debug('<<markDirty');
     620:     }
     621:
     622:     /**
     623:      * Getter for the dirty objects array.
     624:      *
     625:      * @return array
     626:      * @since 1.0
     627:      */
     628:     public function getDirtyObjects() {
     629:         self::$logger->debug('>>getDirtyObjects()');
     630:         self::$logger->debug('<<getDirtyObjects ['.var_export($this->dirtyObjects, true).']');
     631:         return $this->dirtyObjects;
     632:     }
     633:
     634:     /**
     635:      * Adds a newly created business object to the newObjects array in the session.
     636:      *
     637:      * @param AlphaDAO $object
     638:      * @since 1.0
     639:      */
     640:     public function markNew($object) {
     641:         self::$logger->debug('>>markNew(object=['.var_export($object, true).'])');
     642:
     643:         if(method_exists($this, 'before_markNew_callback'))
     644:             $this->before_markNew_callback();
     645:
     646:         $this->newObjects[count($this->newObjects)] = $object;
     647:
     648:         $_SESSION['newObjects'] = $this->newObjects;
     649:
     650:         if(method_exists($this, 'after_markNew_callback'))
     651:             $this->after_markNew_callback();
     652:
     653:         self::$logger->debug('<<markNew');
     654:     }
     655:
     656:     /**
     657:      * Getter for the new objects array.
     658:      *
     659:      * @return array
     660:      * @since 1.0
     661:      */
     662:     public function getNewObjects() {
     663:         self::$logger->debug('>>getNewObjects()');
     664:         self::$logger->debug('<<getNewObjects ['.var_export($this->newObjects, true).']');
     665:         return $this->newObjects;
     666:     }
     667:
     668:     /**
     669:      * Commits (saves) all of the new and modified (dirty) objects in the unit of work to the database.
     670:      *
     671:      * @throws FailedUnitCommitException
     672:      * @since 1.0
     673:      */
     674:     public function commit() {
     675:         self::$logger->debug('>>commit()');
     676:
     677:         if(method_exists($this, 'before_commit_callback'))
     678:             $this->before_commit_callback();
     679:
     680:         AlphaDAO::begin();
     681:
     682:         $newObjects = $this->getNewObjects();
     683:
     684:         $count = count($newObjects);
     685:
     686:         for ($i = 0; $i < $count; $i++) {
     687:             try {
     688:                 $newObjects[$i]->save();
     689:             }catch (FailedSaveException $e) {
     690:                 throw new FailedUnitCommitException($e->getMessage());
     691:                 self::$logger->error('Failed to save new object of type ['.get_class($newObjects[$i]).'], aborting...');
     692:                 $this->abort();
     693:                 return;
     694:             }catch (LockingException $e) {
     695:                 throw new FailedUnitCommitException($e->getMessage());
     696:                 self::$logger->error('Failed to save new object of type ['.get_class($newObjects[$i]).'], aborting...');
     697:                 $this->abort();
     698:                 return;
     699:             }
     700:         }
     701:
     702:         $dirtyObjects = $this->getDirtyObjects();
     703:
     704:         $count = count($dirtyObjects);
     705:
     706:         for ($i = 0; $i < $count; $i++) {
     707:             try {
     708:                 $dirtyObjects[$i]->save();
     709:             }catch (FailedSaveException $e) {
     710:                 throw new FailedUnitCommitException($e->getMessage());
     711:                 self::$logger->error('Failed to save OID ['.$dirtyObjects[$i]->getID().'] of type ['.get_class($dirtyObjects[$i]).'], aborting...');
     712:                 $this->abort();
     713:                 return;
     714:             }catch (LockingException $e) {
     715:                 throw new FailedUnitCommitException($e->getMessage());
     716:                 self::$logger->error('Failed to save OID ['.$dirtyObjects[$i]->getID().'] of type ['.get_class($dirtyObjects[$i]).'], aborting...');
     717:                 $this->abort();
     718:                 return;
     719:             }
     720:         }
     721:
     722:         try{
     723:             AlphaDAO::commit();
     724:
     725:             $this->clearUnitOfWorkAttributes();
     726:
     727:             if(method_exists($this, 'after_commit_callback'))
     728:                 $this->after_commit_callback();
     729:
     730:             self::$logger->debug('<<commit');
     731:         }catch(FailedSaveException $e) {
     732:             throw new FailedUnitCommitException('Failed to commit the transaction, error is ['.$e->getMessage().']');
     733:             self::$logger->debug('<<commit');
     734:         }
     735:     }
     736:
     737:     /**
     738:      * Method to clearup a cancelled unit of work.
     739:      *
     740:      * @throws AlphaException
     741:      * @since 1.0
     742:      */
     743:     public function abort() {
     744:         self::$logger->debug('>>abort()');
     745:
     746:         if(method_exists($this, 'before_abort_callback'))
     747:             $this->before_abort_callback();
     748:
     749:         try{
     750:             AlphaDAO::rollback();
     751:
     752:             $this->clearUnitOfWorkAttributes();
     753:
     754:             if(method_exists($this, 'after_abort_callback'))
     755:                 $this->after_abort_callback();
     756:
     757:             self::$logger->debug('<<abort');
     758:         }catch(AlphaException $e) {
     759:             throw new AlphaException('Failed to rollback the transaction, error is ['.$e->getMessage().']');
     760:             self::$logger->debug('<<abort');
     761:         }
     762:     }
     763:
     764:     /**
     765:      * Clears the session and object attributes related to unit of work sessions
     766:      */
     767:     private function clearUnitOfWorkAttributes() {
     768:         $_SESSION['unitOfWork'] = null;
     769:         $this->unitOfWork = null;
     770:         $_SESSION['dirtyObjects'] = null;
     771:         $this->dirtyObjects = array();
     772:         $_SESSION['newObjects'] = null;
     773:         $this->newObjects = array();
     774:     }
     775:
     776:     /**
     777:      * Getter for the page title.
     778:      *
     779:      * @return string
     780:      * @since 1.0
     781:      */
     782:     public function getTitle() {
     783:         self::$logger->debug('>>getTitle()');
     784:         self::$logger->debug('<<getTitle ['.$this->title.']');
     785:         return $this->title;
     786:     }
     787:
     788:     /**
     789:      * Setter for the page title.
     790:      *
     791:      * @param string $title
     792:      * @since 1.0
     793:      */
     794:     public function setTitle($title) {
     795:         self::$logger->debug('>>setTitle(title=['.$title.'])');
     796:         self::$logger->debug('<<setTitle');
     797:         $this->title = $title;
     798:     }
     799:
     800:     /**
     801:      * Getter for the page description.
     802:      *
     803:      * @return string
     804:      * @since 1.0
     805:      */
     806:     public function getDescription() {
     807:         self::$logger->debug('>>getDescription()');
     808:         self::$logger->debug('<<getDescription ['.$this->description.']');
     809:         return $this->description;
     810:     }
     811:
     812:     /**
     813:      * Setter for the page description.
     814:      *
     815:      * @param string $description
     816:      * @since 1.0
     817:      */
     818:     public function setDescription($description) {
     819:         self::$logger->debug('>>setDescription(description=['.$description.'])');
     820:         self::$logger->debug('<<setDescription');
     821:         $this->description = $description;
     822:     }
     823:
     824:     /**
     825:      * Getter for the page keywords.
     826:      *
     827:      * @return string
     828:      * @since 1.0
     829:      */
     830:     public function getKeywords() {
     831:         self::$logger->debug('>>getKeywords()');
     832:         self::$logger->debug('<<getKeywords ['.$this->keywords.']');
     833:         return $this->keywords;
     834:     }
     835:
     836:     /**
     837:      * Setter for the page keywords, should pass a comma-seperated list as a string.
     838:      *
     839:      * @param string $keywords
     840:      * @since 1.0
     841:      */
     842:     public function setKeywords($keywords) {
     843:         self::$logger->debug('>>setKeywords(keywords=['.$keywords.'])');
     844:         self::$logger->debug('<<setKeywords');
     845:         $this->keywords = $keywords;
     846:     }
     847:
     848:     /**
     849:      * Method to display an access error for trespassing users.  HTTP response header code will be 403.
     850:      *
     851:      * @since 1.0
     852:      */
     853:     public function accessError() {
     854:         self::$logger->debug('>>accessError()');
     855:
     856:         if(method_exists($this, 'before_accessError_callback'))
     857:             $this->before_accessError_callback();
     858:
     859:         global $config;
     860:
     861:         if(isset($_SESSION['currentUser']))
     862:             self::$logger->warn('The user ['.$_SESSION['currentUser']->get('email').'] attempted to access the resource ['.$_SERVER['REQUEST_URI'].'] but was denied due to insufficient rights');
     863:         else
     864:             self::$logger->warn('An unknown user attempted to access the resource ['.$_SERVER['REQUEST_URI'].'] but was denied due to insufficient rights');
     865:
     866:         header('HTTP/1.1 403 Forbidden');
     867:         $front = new FrontController();
     868:         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.');
     869:
     870:         if(method_exists($this, 'after_accessError_callback'))
     871:             $this->after_accessError_callback();
     872:
     873:         self::$logger->debug('<<accessError');
     874:     }
     875:
     876:     /**
     877:      * Checks the user rights of the currently logged-in person against the page
     878:      * visibility set for this controller.  Will return false if the user has
     879:      * not got the correct rights.
     880:      *
     881:      * @return boolean
     882:      * @since 1.0
     883:      */
     884:     public function checkRights() {
     885:         self::$logger->debug('>>checkRights()');
     886:
     887:         if(method_exists($this, 'before_checkRights_callback'))
     888:             $this->before_checkRights_callback();
     889:
     890:         // firstly if the page is Public then there is no issue
     891:         if ($this->getVisibility() == 'Public') {
     892:             if(method_exists($this, 'after_checkRights_callback'))
     893:                 $this->after_checkRights_callback();
     894:
     895:             self::$logger->debug('<<checkRights [true]');
     896:             return true;
     897:         }else{
     898:             // the person is logged in?
     899:             if (isset($_SESSION['currentUser'])) {
     900:                 // checking for admins (can access everything)
     901:                 if ($_SESSION['currentUser']->inGroup('Admin')) {
     902:                     if(method_exists($this, 'after_checkRights_callback'))
     903:                         $this->after_checkRights_callback();
     904:
     905:                     self::$logger->debug('<<checkRights [true]');
     906:                     return true;
     907:                 } elseif ($_SESSION['currentUser']->inGroup($this->getVisibility())) {
     908:                     if(method_exists($this, 'after_checkRights_callback'))
     909:                         $this->after_checkRights_callback();
     910:
     911:                     self::$logger->debug('<<checkRights [true]');
     912:                     return true;
     913:                 // the person is editing their own profile which is allowed
     914:                 } elseif (get_class($this->BO) == 'PersonObject' && $_SESSION['currentUser']->getDisplayName() == $this->BO->getDisplayName()) {
     915:                     if(method_exists($this, 'after_checkRights_callback'))
     916:                         $this->after_checkRights_callback();
     917:
     918:                     self::$logger->debug('<<checkRights [true]');
     919:                     return true;
     920:                 }else{
     921:                     self::$logger->debug('<<checkRights [false]');
     922:                     return false;
     923:                 }
     924:             }else{ // the person is NOT logged in
     925:                 self::$logger->debug('<<checkRights [false]');
     926:                 return false;
     927:             }
     928:         }
     929:     }
     930:
     931:     /**
     932:      * Method to check the validity of the two hidden form security
     933:      * fields which aim to ensure that a post to the controller is being sent from
     934:      * the same server that is hosting it.
     935:      *
     936:      * @return boolean
     937:      * @since 1.0
     938:      */
     939:     public static function checkSecurityFields() {
     940:         if(self::$logger == null)
     941:             self::$logger = new Logger('AlphaController');
     942:         self::$logger->debug('>>checkSecurityFields()');
     943:
     944:         // the server hostname + today's date
     945:         $var1 = base64_encode(AlphaSecurityUtils::encrypt($_SERVER['HTTP_HOST'].date("Ymd")));
     946:         // the server's IP plus $var1
     947:         $var2 = base64_encode(AlphaSecurityUtils::encrypt($_SERVER['REMOTE_ADDR'].$var1));
     948:
     949:         if(empty($_REQUEST['var1']) || empty($_REQUEST['var2'])) {
     950:             self::$logger->warn('The required var1/var2 params where not provided on the HTTP request');
     951:             self::$logger->debug('<<checkSecurityFields [false]');
     952:             return false;
     953:         }
     954:
     955:         if ($var1 == $_REQUEST['var1'] && $var2 == $_REQUEST['var2']) {
     956:             self::$logger->debug('<<checkSecurityFields [true]');
     957:             return true;
     958:         }else{
     959:             /*
     960:              * Here we are implementing a "grace period" of one hour if the time is < 1:00AM, we will accept
     961:              * a match for yesterday's date in the security fields
     962:              *
     963:              */
     964:
     965:             // the server hostname + today's date less 1 hour (i.e. yesterday where time is < 1:00AM)
     966:             $var1 = base64_encode(AlphaSecurityUtils::encrypt($_SERVER['HTTP_HOST'].date("Ymd", (time()-3600))));
     967:             // the server's IP plus $var1
     968:             $var2 = base64_encode(AlphaSecurityUtils::encrypt($_SERVER['REMOTE_ADDR'].$var1));
     969:
     970:             if ($var1 == $_REQUEST['var1'] && $var2 == $_REQUEST['var2']) {
     971:                 self::$logger->debug('<<checkSecurityFields [true]');
     972:                 return true;
     973:             }else{
     974:                 self::$logger->warn('The var1/var2 params provided are invalid, values: var1=['.$_REQUEST['var1'].'] var2=['.$_REQUEST['var2'].']');
     975:                 self::$logger->debug('<<checkSecurityFields [false]');
     976:                 return false;
     977:             }
     978:         }
     979:     }
     980:
     981:     /**
     982:      * Generates the two security fields to prevent remote form processing.
     983:      *
     984:      * @return array An array containing the two fields
     985:      * @since 1.0
     986:      */
     987:     public static function generateSecurityFields() {
     988:         if(self::$logger == null)
     989:             self::$logger = new Logger('AlphaController');
     990:         self::$logger->debug('>>generateSecurityFields()');
     991:
     992:         // the server hostname + today's date
     993:         $var1 = base64_encode(AlphaSecurityUtils::encrypt($_SERVER['HTTP_HOST'].date("Ymd")));
     994:         // the server's IP plus $var1
     995:         $var2 = base64_encode(AlphaSecurityUtils::encrypt($_SERVER['REMOTE_ADDR'].$var1));
     996:
     997:         self::$logger->debug('<<generateSecurityFields [array('.$var1.', '.$var2.')]');
     998:         return array($var1, $var2);
     999:     }
    1000:
    1001:     /**
    1002:      * Returns the name of a custom controller if one is found, otherwise returns null.
    1003:      *
    1004:      * @param string $BOName The classname of the business object
    1005:      * @param string $mode The mode of the controller (create, view, edit)
    1006:      * @return string
    1007:      * @since 1.0
    1008:      */
    1009:     public static function getCustomControllerName($BOName, $mode) {
    1010:         if(self::$logger == null)
    1011:             self::$logger = new Logger('AlphaController');
    1012:         self::$logger->debug('>>getCustomControllerName(BOName=['.$BOName.'], mode=['.$mode.'])');
    1013:
    1014:         global $config;
    1015:
    1016:         // strip the Object part from the class name
    1017:         $BOName = mb_substr($BOName, 0, mb_strpos($BOName, 'Object'));
    1018:         // uppercase the first letter of each word, e.g. create cart becomes Create Cart
    1019:         $controllerName = ucwords($mode.' '.$BOName);
    1020:         // remove spaces
    1021:         $controllerName = str_replace(' ', '', $controllerName);
    1022:
    1023:         self::$logger->debug('Custom controller name is ['.$controllerName.']');
    1024:
    1025:         if (file_exists($config->get('app.root').'controller/'.$controllerName.'.php')) {
    1026:             self::$logger->debug('<<getCustomControllerName');
    1027:             return $controllerName;
    1028:         }elseif (file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php')) {
    1029:             self::$logger->debug('<<getCustomControllerName');
    1030:             return $controllerName;
    1031:         }else{
    1032:             self::$logger->debug('<<getCustomControllerName');
    1033:             return null;
    1034:         }
    1035:     }
    1036:
    1037:     /**
    1038:      * Does a HTTP redirect to a custom controller if one is found.
    1039:      *
    1040:      * @param string $BOName The classname of the business object
    1041:      * @param string $mode The mode of the controller (create, view, edit)
    1042:      * @throws FileNotFoundException
    1043:      * @since 1.0
    1044:      */
    1045:     protected function loadCustomController($BOName, $mode) {
    1046:         self::$logger->debug('>>loadCustomController(BOName=['.$BOName.'], mode=['.$mode.'])');
    1047:
    1048:         global $config;
    1049:
    1050:         // strip the Object part from the class name
    1051:         $BOName = mb_substr($BOName, 0, mb_strpos($BOName, 'Object'));
    1052:         // uppercase the first letter of each word, e.g. create cart becomes Create Cart
    1053:         $controllerName = ucwords($mode.' '.$BOName);
    1054:         // remove spaces
    1055:         $controllerName = str_replace(' ', '', $controllerName);
    1056:
    1057:         self::$logger->debug('Custom controller name is ['.$controllerName.']');
    1058:
    1059:         // just making sure that we are not already using the custom controller
    1060:         if(get_class($this) != $controllerName) {
    1061:             if (file_exists($config->get('app.root').'controller/'.$controllerName.'.php')) {
    1062:                 self::$logger->debug('Custom controller found, redirecting...');
    1063:                 // handle secure URLs
    1064:                 if(isset($_GET['tk'])) {
    1065:                     $params = FrontController::decodeQueryParams($_GET['tk']);
    1066:                     $params = preg_replace('/act=.*\&/', 'act='.$controllerName.'&', $params);
    1067:                     self::$logger->debug('Params are ['.$params.']');
    1068:
    1069:                     $url = FrontController::generateSecureURL($params);
    1070:                     self::$logger->debug('Redirecting to ['.$url.']');
    1071:                     header('Location: '.$url);
    1072:                     self::$logger->debug('<<loadCustomController');
    1073:                     exit;
    1074:                 }else{
    1075:                     $url = $config->get('app.url').'controller/'.$controllerName.'.php?'.$_SERVER['QUERY_STRING'];
    1076:                     self::$logger->debug('Redirecting to ['.$url.']');
    1077:                     header('Location: '.$url);
    1078:                     self::$logger->debug('<<loadCustomController');
    1079:                     exit;
    1080:                 }
    1081:             }elseif (file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php')) {
    1082:                 self::$logger->debug('Custom controller found, redirecting...');
    1083:                 // handle secure URLs
    1084:                 if(self::checkIfAccessingFromSecureURL()) {
    1085:                     if(isset($_GET['tk'])) {
    1086:                         $params = FrontController::decodeQueryParams($_GET['tk']);
    1087:                     }else{
    1088:                         $start = mb_strpos($_SERVER['REQUEST_URI'], '/tk/')+3;
    1089:                         $end = mb_strlen($_SERVER['REQUEST_URI']);
    1090:                         $tk = mb_substr($_SERVER['REQUEST_URI'], $start+1, $end-($start+1));
    1091:                         $params = FrontController::decodeQueryParams($tk);
    1092:                     }
    1093:
    1094:                     $params = preg_replace('/act=.*\&/', 'act='.$controllerName.'&', $params);
    1095:                     self::$logger->debug('Params are ['.$params.']');
    1096:
    1097:                     $url = FrontController::generateSecureURL($params);
    1098:                     self::$logger->debug('Redirecting to ['.$url.']');
    1099:                     header('Location: '.$url);
    1100:                     self::$logger->debug('<<loadCustomController');
    1101:                     exit;
    1102:                 }else{
    1103:                     $url = $config->get('app.url').'alpha/controller/'.$controllerName.'.php?'.$_SERVER['QUERY_STRING'];
    1104:                     self::$logger->debug('Redirecting to ['.$url.']');
    1105:                     header('Location: '.$url);
    1106:                     self::$logger->debug('<<loadCustomController');
    1107:                     exit;
    1108:                 }
    1109:             }else{
    1110:                 // throw an exception if we have gotten this far and no custom controller was found
    1111:                 throw new FileNotFoundException('The controller ['.$controllerName.'] could not be loaded as it does not exist');
    1112:             }
    1113:         }
    1114:
    1115:         self::$logger->debug('<<loadCustomController');
    1116:     }
    1117:
    1118:     /**
    1119:      * Set the status message in the _SESSION to the value provided.
    1120:      *
    1121:      * @param string $message
    1122:      * @since 1.0
    1123:      */
    1124:     public function setStatusMessage($message) {
    1125:         $this->statusMessage = $message;
    1126:         $_SESSION['statusMessage'] = $message;
    1127:     }
    1128:
    1129:     /**
    1130:      * Gets the current status message for this controller.  Note that by getting the current
    1131:      * status message, you clear out the value stored in _SESSION so this method can only be used
    1132:      * to get the status message once for display purposes.
    1133:      *
    1134:      * @return string
    1135:      * @since 1.0
    1136:      */
    1137:     public function getStatusMessage() {
    1138:         $_SESSION['statusMessage'] = null;
    1139:         return $this->statusMessage;
    1140:     }
    1141:
    1142:     /**
    1143:      * Checks that the definition for the controller classname provided exists.  Will also return true
    1144:      * if you pass "/" for the root of the web application.
    1145:      *
    1146:      * @param string $controllerName
    1147:      * @return boolean
    1148:      * @since 1.0
    1149:      */
    1150:     public static function checkControllerDefExists($controllerName) {
    1151:         if(self::$logger == null)
    1152:             self::$logger = new Logger('AlphaController');
    1153:         self::$logger->debug('>>checkControllerDefExists(controllerName=['.$controllerName.'])');
    1154:
    1155:         global $config;
    1156:
    1157:         $exists = false;
    1158:
    1159:         if($controllerName == '/')
    1160:             $exists = true;
    1161:         if(file_exists($config->get('app.root').'controller/'.$controllerName.'.php'))
    1162:             $exists = true;
    1163:         if(file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php'))
    1164:             $exists = true;
    1165:
    1166:         self::$logger->debug('<<checkControllerDefExists ['.$exists.']');
    1167:         return $exists;
    1168:     }
    1169:
    1170:     /**
    1171:      * Loads the definition for the controller classname provided.
    1172:      *
    1173:      * @param string $controllerName
    1174:      * @throws IllegalArguementException
    1175:      * @since 1.0
    1176:      */
    1177:     public static function loadControllerDef($controllerName) {
    1178:         if(self::$logger == null)
    1179:             self::$logger = new Logger('AlphaController');
    1180:         self::$logger->debug('>>loadControllerDef(controllerName=['.$controllerName.'])');
    1181:
    1182:         global $config;
    1183:
    1184:         if(file_exists($config->get('app.root').'controller/'.$controllerName.'.php'))
    1185:             require_once $config->get('app.root').'controller/'.$controllerName.'.php';
    1186:         elseif(file_exists($config->get('app.root').'alpha/controller/'.$controllerName.'.php'))
    1187:             require_once $config->get('app.root').'alpha/controller/'.$controllerName.'.php';
    1188:         else
    1189:             throw new IllegalArguementException('The class ['.$controllerName.'] is not defined anywhere!');
    1190:
    1191:         self::$logger->debug('<<loadControllerDef');
    1192:     }
    1193:
    1194:     /**
    1195:      * Static function for determining if the current request URL is a secure one (has a tk string or not)
    1196:      *
    1197:      * @return boolean True if the current URL contains a tk value, false otherwise
    1198:      * @since 1.0
    1199:      */
    1200:     public static function checkIfAccessingFromSecureURL() {
    1201:         if (isset($_GET['tk']) || mb_strpos($_SERVER['REQUEST_URI'], '/tk/') !== false)
    1202:             return true;
    1203:         else
    1204:             return false;
    1205:     }
    1206:
    1207:     /**
    1208:      * Descrypts the POST fieldnames in the global $_REQUEST and $_POST variables
    1209:      *
    1210:      * @since 1.2.2
    1211:      */
    1212:     private function decryptFieldNames() {
    1213:         foreach(array_keys($_POST) as $fieldname) {
    1214:
    1215:             // set request params where fieldnames provided are based64 encoded and encrypted
    1216:             if(AlphaValidator::isBase64($fieldname)) {
    1217:                 $_REQUEST[trim(AlphaSecurityUtils::decrypt(base64_decode($fieldname)))] = $_POST[$fieldname];
    1218:                 $_POST[trim(AlphaSecurityUtils::decrypt(base64_decode($fieldname)))] = $_POST[$fieldname];
    1219:             }
    1220:
    1221:             // clear non-encrypted fields (we won't accept these when security.encrypt.http.fieldnames = true), and encrypted fieldnames already decrypted above.
    1222:             unset($_POST[$fieldname]);
    1223:             unset($_REQUEST[$fieldname]);
    1224:
    1225:         }
    1226:     }
    1227: }
    1228:
    1229: ?>