PageRenderTime 47ms CodeModel.GetById 17ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 1ms

/library/core/class.dispatcher.php

https://github.com/wufoo/Garden
PHP | 625 lines | 333 code | 88 blank | 204 comment | 86 complexity | c37697f0507b73d8965a1f59f4ce76d1 MD5 | raw file
  1<?php if (!defined('APPLICATION')) exit();
  2/*
  3Copyright 2008, 2009 Vanilla Forums Inc.
  4This file is part of Garden.
  5Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  6Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  7You should have received a copy of the GNU General Public License along with Garden.  If not, see <http://www.gnu.org/licenses/>.
  8Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
  9*/
 10
 11/**
 12 * Dispatcher handles all requests.
 13 *
 14 * @author Mark O'Sullivan
 15 * @copyright 2003 Mark O'Sullivan
 16 * @license http://www.opensource.org/licenses/gpl-2.0.php GPL
 17 * @package Garden
 18 * @version @@GARDEN-VERSION@@
 19 * @namespace Garden.Core
 20 */
 21
 22class Gdn_Dispatcher extends Gdn_Pluggable {
 23
 24   /**
 25    * An array of folders within the application that are OK to search through
 26    * for controllers. This property is filled by the applications array
 27    * located in /conf/applications.php and included in /bootstrap.php
 28    *
 29    * @var array
 30    */
 31   private $_EnabledApplicationFolders;
 32
 33   /**
 34    * An associative array of ApplicationName => ApplicationFolder. This
 35    * property is filled by the applications array located in
 36    * /conf/applications.php and included in /bootstrap.php
 37    *
 38    * @var array
 39    */
 40   private $_EnabledApplications;
 41
 42   /**
 43    * The currently requested url (defined in _AnalyzeRequest)
 44    *
 45    * @var string
 46    */
 47   public $Request;
 48
 49   /**
 50    * The name of the application folder that contains the controller that has
 51    * been requested.
 52    *
 53    * @var string
 54    */
 55   private $_ApplicationFolder;
 56
 57   /**
 58    * An associative collection of AssetName => Strings that will get passed
 59    * into the controller once it has been instantiated.
 60    *
 61    * @var array
 62    */
 63   private $_AssetCollection;
 64
 65   /**
 66    * The name of the controller folder that contains the controller that has
 67    * been requested.
 68    *
 69    * @var string
 70    */
 71   private $_ControllerFolder;
 72
 73   /**
 74    * The name of the controller to be dispatched.
 75    *
 76    * @var string
 77    */
 78   private $_ControllerName;
 79
 80   /**
 81    * The method of the controller to be called.
 82    *
 83    * @var string
 84    */
 85   private $_ControllerMethod;
 86
 87   /**
 88    * Any query string arguments supplied to the controller method.
 89    *
 90    * @var string
 91    */
 92   private $_ControllerMethodArgs = array();
 93
 94   /**
 95    * @var string|FALSE The delivery method to set on the controller.
 96    */
 97   private $_DeliveryMethod = FALSE;
 98
 99
100   /**
101    * @var string|FALSE The delivery type to set on the controller.
102    */
103   private $_DeliveryType = FALSE;
104
105   /**
106    * An associative collection of variables that will get passed into the
107    * controller as properties once it has been instantiated.
108    *
109    * @var array
110    */
111   private $_PropertyCollection;
112
113   /**
114    * Defined by the url of the request: SYNDICATION_RSS, SYNDICATION_ATOM, or
115    * SYNDICATION_NONE (default).
116    *
117    * @var string
118    */
119   private $_SyndicationMethod;
120
121   /**
122    * Class constructor.
123    */
124   public function __construct() {
125      parent::__construct();
126      $this->_EnabledApplicationFolders = array();
127      $this->Request = '';
128      $this->_ApplicationFolder = '';
129      $this->_AssetCollection = array();
130      $this->_ControllerFolder = '';
131      $this->_ControllerName = '';
132      $this->_ControllerMethod = '';
133      $this->_ControllerMethodArgs = array();
134      $this->_PropertyCollection = array();
135   }
136   
137   public function Cleanup() {
138      // Destruct the db connection;
139      $Database = Gdn::Database();
140      if($Database != null)
141         $Database->CloseConnection();
142   }
143
144
145   /**
146    * Return the properly formatted controller class name.
147    */
148   public function ControllerName() {
149      return $this->_ControllerName.'Controller';
150   }
151   
152   public function Application() {
153      return $this->_ApplicationFolder;
154   }
155   
156   public function Controller() {
157      return $this->_ControllerName;
158   }
159   
160   public function ControllerMethod() {
161      return $this->_ControllerMethod;
162   }
163   
164   public function ControllerArguments() {
165      return $this->_ControllerMethodArgs;
166   }
167
168   /**
169    * Analyzes the supplied query string and decides how to dispatch the request.
170    */
171   public function Dispatch($ImportRequest = NULL, $Permanent = TRUE) {
172      if ($ImportRequest && is_string($ImportRequest))
173         $ImportRequest = Gdn_Request::Create()->FromEnvironment()->WithURI($ImportRequest);
174      
175      if (is_a($ImportRequest, 'Gdn_Request') && $Permanent) {
176         Gdn::Request($ImportRequest);
177      }
178      
179      $Request = is_a($ImportRequest, 'Gdn_Request') ? $ImportRequest : Gdn::Request();
180   
181      if (Gdn::Config('Garden.UpdateMode', FALSE)) {
182         if (!Gdn::Session()->CheckPermission('Garden.Settings.GlobalPrivs')) {
183            // Updatemode, and this user is not root admin
184            $Request->WithURI(Gdn::Router()->GetDestination('UpdateMode'));
185         }
186      }
187      
188      $this->FireEvent('BeforeDispatch');
189      $this->_AnalyzeRequest($Request);
190      
191      // Send user to login page if this is a private community (with some minor exceptions)
192      if (
193         C('Garden.PrivateCommunity')
194         && $this->ControllerName() != 'EntryController'
195         && !Gdn::Session()->IsValid()
196         && !InArrayI($this->ControllerMethod(), array('UsernameAvailable', 'EmailAvailable', 'TermsOfService'))
197      ) {
198         Redirect(Gdn::Authenticator()->SignInUrl($this->Request));
199         exit();
200      }
201         
202      /*
203      echo "<br />Gdn::Request thinks: ".Gdn::Request()->Path();
204      echo "<br />Gdn::Request also suggests: output=".Gdn::Request()->OutputFormat().", filename=".Gdn::Request()->Filename();
205      echo '<br />Request: '.$this->Request;      
206      echo '<br />App folder: '.$this->_ApplicationFolder;
207      echo '<br />Controller folder: '.$this->_ControllerFolder;
208      echo '<br />ControllerName: '.$this->_ControllerName;
209      echo '<br />ControllerMethod: '.$this->_ControllerMethod;
210      */
211
212      $ControllerName = $this->ControllerName();
213      if ($ControllerName != '' && class_exists($ControllerName)) {
214         // Create it and call the appropriate method/action
215         $Controller = new $ControllerName();
216
217         // Pass along any assets
218         if (is_array($this->_AssetCollection)) {
219            foreach ($this->_AssetCollection as $AssetName => $Assets) {
220               foreach ($Assets as $Asset) {
221                  $Controller->AddAsset($AssetName, $Asset);
222               }
223            }
224         }
225
226         // Instantiate Imported & Uses classes
227         $Controller->GetImports();
228
229         // Pass in the syndication method
230         $Controller->SyndicationMethod = $this->_SyndicationMethod;
231
232         // Pass along the request
233         $Controller->SelfUrl = $this->Request;
234
235         // Pass along any objects
236         foreach($this->_PropertyCollection as $Name => $Mixed) {
237            $Controller->$Name = $Mixed;
238         }
239
240         // Set up a default controller method in case one isn't defined.
241         $ControllerMethod = str_replace('_', '', $this->_ControllerMethod);
242         $Controller->OriginalRequestMethod = $ControllerMethod;
243         
244         $this->FireEvent('AfterAnalyzeRequest');
245         
246         // Take enabled plugins into account, as well
247         $PluginManagerHasReplacementMethod = Gdn::PluginManager()->HasNewMethod($this->ControllerName(), $this->_ControllerMethod);
248         if (!$PluginManagerHasReplacementMethod && ($this->_ControllerMethod == '' || !method_exists($Controller, $ControllerMethod))) {
249            // Check to see if there is an 'x' version of the method.
250            if (method_exists($Controller, 'x'.$ControllerMethod)) {
251               // $PluginManagerHasReplacementMethod = TRUE;
252               $ControllerMethod = 'x'.$ControllerMethod;
253            } else {
254               if ($this->_ControllerMethod != '')
255                  array_unshift($this->_ControllerMethodArgs, $this->_ControllerMethod);
256               
257               $this->_ControllerMethod = 'Index';
258               $ControllerMethod = 'Index';
259               
260               $PluginManagerHasReplacementMethod = Gdn::PluginManager()->HasNewMethod($this->ControllerName(), $this->_ControllerMethod);
261            }
262         }
263         
264         // Pass in the querystring values
265         $Controller->ApplicationFolder = $this->_ApplicationFolder;
266         $Controller->Application = $this->EnabledApplication();
267         $Controller->ControllerFolder = $this->_ControllerFolder;
268         $Controller->RequestMethod = $this->_ControllerMethod;
269         $Controller->RequestArgs = $this->_ControllerMethodArgs;
270         $Controller->Request = $Request;
271         $Controller->DeliveryType($Request->GetValue('DeliveryType', $this->_DeliveryType));
272         $Controller->DeliveryMethod($Request->GetValue('DeliveryMethod', $this->_DeliveryMethod));
273
274         $this->FireEvent('BeforeControllerMethod');
275
276         // Set special controller method options for REST APIs.
277         $this->_ReflectControllerArgs($Controller);
278         
279         $Controller->Initialize();
280
281         // Call the requested method on the controller - error out if not defined.
282         if ($PluginManagerHasReplacementMethod || method_exists($Controller, $ControllerMethod)) {
283            // call_user_func_array is too slow!!
284            //call_user_func_array(array($Controller, $ControllerMethod), $this->_ControllerMethodArgs);
285            
286            if ($PluginManagerHasReplacementMethod) {
287              Gdn::PluginManager()->CallNewMethod($Controller, $Controller->ControllerName, $ControllerMethod);
288            } else { 
289              $Args = $this->_ControllerMethodArgs;
290              $Count = count($Args);
291
292              try {
293                 call_user_func_array(array($Controller, $ControllerMethod), $Args);
294              } catch (Exception $Ex) {
295                 $Controller->RenderException($Ex);
296                 exit();
297              }
298            }
299         } else {
300            Gdn::Request()->WithRoute('Default404');
301            return $this->Dispatch();
302         }
303      }
304   }
305   
306   /**
307    * Undocumented method.
308    *
309    * @param string $EnabledApplications
310    * @todo Method EnabledApplicationFolders() and $EnabledApplications needs descriptions.
311    */
312   public function EnabledApplicationFolders($EnabledApplications = '') {
313      if ($EnabledApplications != '' && count($this->_EnabledApplicationFolders) == 0) {
314         $this->_EnabledApplications = $EnabledApplications;
315         $this->_EnabledApplicationFolders = array_values($EnabledApplications);
316      }
317      return $this->_EnabledApplicationFolders;
318   }
319
320   /**
321    * Returns the name of the enabled application based on $ApplicationFolder.
322    *
323    * @param string The application folder related to the application name you want to return.
324    */
325   public function EnabledApplication($ApplicationFolder = '') {
326      if ($ApplicationFolder == '')
327         $ApplicationFolder = $this->_ApplicationFolder;
328
329      $EnabledApplication = array_keys($this->_EnabledApplications, $ApplicationFolder);
330      $EnabledApplication = count($EnabledApplication) > 0 ? $EnabledApplication[0] : '';
331      $this->EventArguments['EnabledApplication'] = $EnabledApplication;
332      $this->FireEvent('AfterEnabledApplication');
333      return $EnabledApplication;
334   }
335
336   /**
337    * Allows the passing of a string to the controller's asset collection.
338    *
339    * @param string $AssetName The name of the asset collection to add the string to.
340    * @param mixed $Asset The string asset to be added. The asset can be one of two things.
341    * - <b>string</b>: The string will be rendered to the page.
342    * - <b>Gdn_IModule</b>: The Gdn_IModule::Render() method will be called when the asset is rendered.
343    */
344   public function PassAsset($AssetName, $Asset) {
345      $this->_AssetCollection[$AssetName][] = $Asset;
346   }
347
348   /**
349    * Allows the passing of any variable to the controller as a property.
350    *
351    * @param string $Name The name of the property to assign the variable to.
352    * @param mixed $Mixed The variable to be passed as a property of the controller.
353    */
354   public function PassProperty($Name, $Mixed) {
355      $this->_PropertyCollection[$Name] = $Mixed;
356   }
357
358   /**
359    * Parses the query string looking for supplied request parameters. Places
360    * anything useful into this object's Controller properties.
361    *
362    * @param int $FolderDepth
363    * @todo $folderDepth needs a description.
364    */
365   protected function _AnalyzeRequest(&$Request, $FolderDepth = 1) {
366      // Here is the basic format of a request:
367      // [/application]/controller[/method[.json|.xml]]/argn|argn=valn
368
369      // Here are some examples of what this method could/would receive:
370      // /application/controller/method/argn
371      // /controller/method/argn
372      // /application/controller/argn
373      // /controller/argn
374      // /controller
375
376
377      // Clear the slate
378      $this->_ApplicationFolder = '';
379      $this->_ControllerFolder = '';
380      $this->_ControllerName = '';
381      $this->_ControllerMethod = 'index';
382      $this->_ControllerMethodArgs = array();
383      $this->Request = $Request->Path(TRUE);
384
385      $PathAndQuery = $Request->PathAndQuery();
386      //$Router = Gdn::Router();
387      $MatchRoute = Gdn::Router()->MatchRoute($PathAndQuery);
388
389      // We have a route. Take action.
390      if ($MatchRoute !== FALSE) {
391         switch ($MatchRoute['Type']) {
392            case 'Internal':
393               $Request->PathAndQuery($MatchRoute['FinalDestination']);
394               $this->Request = $MatchRoute['FinalDestination'];
395               break;
396
397            case 'Temporary':
398               header( "HTTP/1.1 302 Moved Temporarily" );
399               header( "Location: ".$MatchRoute['FinalDestination'] );
400               exit();
401               break;
402
403            case 'Permanent':
404               header( "HTTP/1.1 301 Moved Permanently" );
405               header( "Location: ".$MatchRoute['FinalDestination'] );
406               exit();
407               break;
408
409            case 'NotAuthorized':
410               header( "HTTP/1.1 401 Not Authorized" );
411               $this->Request = $MatchRoute['FinalDestination'];
412               break;
413
414            case 'NotFound':
415               header( "HTTP/1.1 404 Not Found" );
416               $this->Request = $MatchRoute['FinalDestination'];
417               break;
418         }
419      }
420
421
422      switch ($Request->OutputFormat()) {
423         case 'rss':
424            $this->_SyndicationMethod = SYNDICATION_RSS;
425            break;
426         case 'atom':
427            $this->_SyndicationMethod = SYNDICATION_ATOM;
428            break;
429         case 'default':
430         default:
431            $this->_SyndicationMethod = SYNDICATION_NONE;
432            break;
433      }
434
435      if ($this->Request == '')
436      {
437         $DefaultController = Gdn::Router()->GetRoute('DefaultController');
438         $this->Request = $DefaultController['Destination'];
439      }
440   
441      $Parts = explode('/', $this->Request);
442      $Length = count($Parts);
443      if ($Length == 1 || $FolderDepth <= 0) {
444         $FolderDepth = 0;
445         list($this->_ControllerName, $this->_DeliveryMethod) = $this->_SplitDeliveryMethod($Parts[0]);
446         $Parts[0] = $this->_ControllerName;
447         $this->_MapParts($Parts, 0);
448         $this->_FetchController(TRUE); // Throw an error if this fails because there's nothing else to check
449      } else if ($Length == 2) {
450         // Force a depth of 1 because only one of the two url parts can be a folder.
451         $FolderDepth = 1;
452      }
453      if ($FolderDepth == 2) {
454         $this->_ApplicationFolder = $Parts[0];
455         $this->_ControllerFolder = $Parts[1];
456         $this->_MapParts($Parts, 2);
457
458         if (!$this->_FetchController()) {
459            // echo '<div>Failed. AppFolder: '.$this->_ApplicationFolder.'; Cont Folder: '.$this->_ControllerFolder.'; Cont: '.$this->_ControllerName.';</div>';
460            $this->_AnalyzeRequest($Request, 1);
461         }
462
463      } else if ($FolderDepth == 1) {
464         // Try the application folder first
465         $Found = FALSE;
466         if (in_array($Parts[0], $this->EnabledApplicationFolders())) {
467            // Check to see if the first part is an application
468            $this->_ApplicationFolder = $Parts[0];
469            $this->_MapParts($Parts, 1);
470            $Found = $this->_FetchController();
471         }
472         if (!$Found) {
473            // echo '<div>Failed. AppFolder: '.$this->_ApplicationFolder.'; Cont Folder: '.$this->_ControllerFolder.'; Cont: '.$this->_ControllerName.';</div>';
474            // Check to see if the first part is a controller folder
475            $this->_ApplicationFolder = '';
476            $this->_ControllerFolder = $Parts[0];
477            $this->_MapParts($Parts, 1);
478            if (!$this->_FetchController()) {
479               // echo '<div>Failed. AppFolder: '.$this->_ApplicationFolder.'; Cont Folder: '.$this->_ControllerFolder.'; Cont: '.$this->_ControllerName.';</div>';
480               $this->_AnalyzeRequest($Request, 0);
481            }
482         }
483      }
484      if (in_array($this->_DeliveryMethod, array(DELIVERY_METHOD_JSON, DELIVERY_METHOD_XML)))
485         $this->_DeliveryType = DELIVERY_TYPE_DATA;
486   }
487
488   /**
489    * Searches through the /cache/controller_mappings.php file for the requested
490    * controller. If it doesn't find it, it searches through the entire
491    * application's folders for the requested controller. If it finds the
492    * controller, it adds the mapping to /cache/controller_mappings.php so it
493    * won't need to search again. If it doesn't find the controller file
494    * anywhere, it throws a fatal error.
495    *
496    * @param boolean $ThrowErrorOnFailure
497    * @todo $ThrowErrorOnFailure needs a description.
498    */
499   private function _FetchController($ThrowErrorOnFailure = FALSE) {
500      $ControllerWhiteList = $this->EnabledApplicationFolders();
501      // Don't include it if it's already been included
502      if (!class_exists($this->ControllerName())) {
503         $PathParts = array('controllers');
504         if ($this->_ControllerFolder != '')
505            $PathParts[] = $this->_ControllerFolder;
506
507         $PathParts[] = 'class.'.strtolower($this->_ControllerName).'controller.php';
508         $ControllerFileName = CombinePaths($PathParts);
509
510         // Limit the white list to the specified application folder if it was in the request
511         if ($this->_ApplicationFolder != '' && InArrayI($this->_ApplicationFolder, $ControllerWhiteList))
512            $ControllerWhiteList = array($this->_ApplicationFolder);
513
514         $ControllerPath = Gdn_FileSystem::FindByMapping('controller', PATH_APPLICATIONS, $ControllerWhiteList, $ControllerFileName);
515         if ($ControllerPath !== FALSE) {
516            // Strip the "Application Folder" from the controller path (this is
517            // used by the controller for various purposes. ie. knowing which
518            // application to search in for a view file).
519            $this->_ApplicationFolder = explode(DS, str_replace(PATH_APPLICATIONS . DS, '', $ControllerPath));
520            $this->_ApplicationFolder = $this->_ApplicationFolder[0];
521            $AppControllerName = ucfirst(strtolower($this->_ApplicationFolder)).'Controller';
522
523            // Load the application's master controller
524            if (!class_exists($AppControllerName))
525               require_once(CombinePaths(array(PATH_APPLICATIONS, $this->_ApplicationFolder, 'controllers', 'class.'.strtolower($this->_ApplicationFolder).'controller.php')));
526
527            // Now load the library (no need to check for existence - couldn't
528            // have made it here if it didn't exist).
529            require_once($ControllerPath);
530         }
531      }
532      if (!class_exists($this->ControllerName())) {
533         if ($ThrowErrorOnFailure === TRUE) {
534            if (ForceBool(Gdn::Config('Garden.Debug'))) {
535               trigger_error(ErrorMessage('Controller not found: '.$this->ControllerName(), 'Dispatcher', '_FetchController'), E_USER_ERROR);
536            } else {
537               $MissingRoute = Gdn::Router()->GetRoute('Default404');
538            
539               // Return a 404 message
540               list($this->_ApplicationFolder, $this->_ControllerName, $this->_ControllerMethod) = explode('/', $MissingRoute['Destination']);
541               $ControllerFileName = CombinePaths(array('controllers', 'class.' . strtolower($this->_ControllerName) . 'controller.php'));
542               $ControllerPath = Gdn_FileSystem::FindByMapping('controller', PATH_APPLICATIONS, $ControllerWhiteList, $ControllerFileName);
543               $this->_ApplicationFolder = explode(DS, str_replace(PATH_APPLICATIONS . DS, '', $ControllerPath));
544               $this->_ApplicationFolder = $this->_ApplicationFolder[0];
545               require_once(CombinePaths(array(PATH_APPLICATIONS, $this->_ApplicationFolder, 'controllers', 'class.'.strtolower($this->_ApplicationFolder).'controller.php')));
546               require_once($ControllerPath);
547            }
548         }
549         return FALSE;
550      } else {
551         return TRUE;
552      }
553   }
554   
555   /**
556    * An internal method used to map parts of the request to various properties
557    * of this object that represent the controller, controller method, and
558    * controller method arguments.
559    *
560    * @param array $Parts An array of parts of the request.
561    * @param int $ControllerKey An integer representing the key of the controller in the $Parts array.
562    */
563   private function _MapParts($Parts, $ControllerKey) {
564      $Length = count($Parts);
565      if ($Length > $ControllerKey)
566         $this->_ControllerName = ucfirst(strtolower($Parts[$ControllerKey]));
567
568      if ($Length > $ControllerKey + 1)
569         list($this->_ControllerMethod, $this->_DeliveryMethod) = $this->_SplitDeliveryMethod($Parts[$ControllerKey + 1]);
570
571      if ($Length > $ControllerKey + 2) {
572         for ($i = $ControllerKey + 2; $i < $Length; ++$i) {
573            if ($Parts[$i] != '')
574               $this->_ControllerMethodArgs[] = $Parts[$i];
575         }
576      }
577   }
578
579   protected function _ReflectControllerArgs($Controller) {
580      // Reflect the controller arguments based on the get.
581      if (count($Controller->Request->Get()) == 0)
582         return;
583
584      if (!method_exists($Controller, $this->_ControllerMethod))
585         return;
586
587      $Meth = new ReflectionMethod($Controller, $this->_ControllerMethod);
588      $MethArgs = $Meth->getParameters();
589      $Args = array();
590      $Get = array_change_key_case($Controller->Request->Get());
591      $MissingArgs = array();
592
593      // Set all of the parameters.
594      foreach ($MethArgs as $Index => $MethParam) {
595         $ParamName = strtolower($MethParam->getName());
596
597         if (isset($this->_ControllerMethodArgs[$Index]))
598            $Args[] = $this->_ControllerMethodArgs[$Index];
599         elseif (isset($Get[$ParamName]))
600            $Args[] = $Get[$ParamName];
601         elseif ($MethParam->isDefaultValueAvailable())
602            $Args[] = $MethParam->getDefaultValue();
603         else {
604            $Args[] = NULL;
605            $MissingArgs[] = "$Index: $ParamName";
606         }
607      }
608
609      $this->_ControllerMethodArgs = $Args;
610         
611   }
612
613   protected function _SplitDeliveryMethod($Name) {
614      $Parts = explode('.', $Name, 2);
615      if (count($Parts) >= 2) {
616         if (in_array(strtoupper($Parts[1]), array(DELIVERY_METHOD_JSON, DELIVERY_METHOD_XHTML, DELIVERY_METHOD_XML))) {
617            return array($Parts[0], strtoupper($Parts[1]));
618         } else {
619            return array($Name, $this->_DeliveryMethod);
620         }
621      } else {
622         return array($Name, $this->_DeliveryMethod);
623      }
624   }
625}