Exemplo n.º 1
0
 /**
  * Initialize the named page. This will load the widget instances and prepare for manipulating the UI.
  *
  * Each page has two parts, the HTML template, and the page instances config file (also called the .yaml file).
  * On the filesystem, they are named <pageName>.tpl (the template file) and <pageName>.yaml (the config file).
  *
  * Once the instances are initialized and configured, the module will be given a chance to load default settings for the page via a callback.
  * This is the time to set up select lists, default values, etc.
  * The method name that will be called on the module is "<pageName>_PageDidLoad". Here is the prototype for that method:
  * void <pageName>_PageDidLoad($page, $parameters);
  *
  * The parameters argument is an associative array, with "name" => "value". The items that will be in the array are determined by the page's parameterList,
  * which is provided by the <pageName>_ParameterList() method, you can implement if your page needs parameters. This method should simply return a list of
  * strings, which will become the "names" passed into your _PageDidLoad method.
  *
  * Here's how the parameterization works... for each item in the array, first the PATH_INFO is mapped to the items. So, if your parameterList is:
  *
  * array('itemID', 'otherParameter')
  *
  * And the PATH_INFO is /12/moreData then the parameters passed in will be array('itemID' => '12', 'otherParameter' => 'moreData').
  * Any data that cannot be matched up will be passed with a NULL value.
  * Also, if there is a form submitted, then the values for the parameters will be replaced by the "value" of the outlets of the same name.
  * Thus, if your form has an "itemID" hidden field, the value from the form will supercede the value from PATH_INFO.
  *
  * After the _PageDidLoad call, the UI state from the request will be applied on top of the defaults.
  *
  * @todo Something is goofy with the detection of isRequestPage surrounding the action processor... seems to be getting called on the response page too. investiage.
  * @todo Rename this to executePage or something... this actually runs the whole page infrastructure!
  */
 function initPage($pageName)
 {
     WFLog::log("Initing page {$pageName}", WFLog::TRACE_LOG);
     if (!empty($this->pageName)) {
         throw new Exception("Page already inited with: {$this->pageName}. Cannot initPage twice.");
     }
     $this->pageName = $pageName;
     // look for page delegate
     $pageDelegateClassName = $this->module->moduleName() . '_' . $this->pageName;
     if (class_exists($pageDelegateClassName, false)) {
         $delegate = new $pageDelegateClassName();
         // look for case mismatch b/w instantiated name and name in URL; php class names are case-insensitive, so class_exists() can kinda false-positive on us here
         // the mistmatch of cases means that downstream users of the name (loading yaml files, etc) will unexpectedly fail.
         if ($pageDelegateClassName !== get_class($delegate)) {
             throw new WFRequestController_NotFoundException("Page {$this->pageName} not found.");
             // $this->pageName = substr(get_class($delegate), strlen($this->module->moduleName() . '_')); // upgrade name to correct for case
         }
         $this->setDelegate($delegate);
     }
     // calculate various file paths
     $basePagePath = $this->module->pathToPage($this->pageName);
     $yamlFile = $basePagePath . '.yaml';
     $instancesFile = $basePagePath . '.instances';
     $configFile = $basePagePath . '.config';
     if (file_exists($yamlFile)) {
         WFLog::log("Loading YAML config: {$pageName}.yaml", WFLog::TRACE_LOG);
         $yamlConfig = WFYaml::load($yamlFile);
         //print_r($yamlConfig);
         foreach ($yamlConfig as $id => $instanceManifest) {
             $this->initInstanceYAML($id, $instanceManifest);
         }
         // after all config info is loaded, certain widget types need to "update" things...
         // since we don't control the order of property loading (that would get way too complex) we just handle some things at the end of the loadConfig
         foreach ($this->instances as $viewId => $view) {
             $view->allConfigFinishedLoading();
         }
     } else {
         // parse instances file and instantiate all objects be graceful -- no need to have fatal error if there are no instances
         if (file_exists($instancesFile)) {
             include $instancesFile;
             foreach ($__instances as $id => $instanceManifest) {
                 $this->initInstancePHP($id, $instanceManifest);
             }
             // parse config file - for each instance, see if there is a config setup for that instance ID and apply it.
             // note: this will call allConfigFinishedLoading automatically
             $this->loadConfig($configFile);
         }
     }
     // let delegate know the page instances are ready
     $this->pageInstancesDidLoad();
     // restore UI state, if this is the requestPage
     // must happen AFTER config is loaded b/c some config options may affect how the widgets interpret the form data.
     // must happen BEFORE _PageDidLoad callback because that callback may need to access widget data, before it's available via bindings.
     if ($this->isRequestPage()) {
         $this->restoreState();
     }
     // the PARAMTERS are ONLY determined for the requestPage!
     // Calculate parameters
     $parameters = array();
     $parameterList = $this->parameterList();
     // get parameter definition from delegate
     if ($this->isRequestPage()) {
         WFLog::log("Parameterizing {$pageName}", WFLog::TRACE_LOG);
         if (count($parameterList) > 0) {
             // first map all items through from PATH_INFO
             // @todo Right now this doesn't allow DEFAULT parameter values (uses NULL). Would be nice if this supported assoc_array so we could have defaults.
             $invocationParameters = $this->module->invocation()->parameters();
             $defaultOpts = array('defaultValue' => NULL, 'greedy' => false);
             $i = 0;
             $lastI = count($parameterList) - 1;
             foreach ($parameterList as $k => $v) {
                 if (gettype($k) === 'integer') {
                     $opts = $defaultOpts;
                     $parameterKey = $v;
                 } else {
                     $parameterKey = $k;
                     if (is_array($v)) {
                         $opts = array_merge($defaultOpts, $v);
                     } else {
                         $opts = $defaultOpts;
                         $opts['defaultValue'] = $v;
                     }
                 }
                 if (isset($invocationParameters[$i])) {
                     // handle greedy
                     if ($i === $lastI and $opts['greedy'] === true and count($invocationParameters) > count($parameterList)) {
                         $parameters[$parameterKey] = join('/', array_slice($invocationParameters, $i));
                     } else {
                         $parameters[$parameterKey] = $invocationParameters[$i];
                     }
                 } else {
                     $parameters[$parameterKey] = isset($_REQUEST[$parameterKey]) ? $_REQUEST[$parameterKey] : $opts['defaultValue'];
                 }
                 $i++;
             }
             // then over-ride with from form, if one has been submitted
             if ($this->hasSubmittedForm()) {
                 foreach ($parameterList as $id) {
                     if (!$this->hasOutlet($id)) {
                         continue;
                     }
                     // see if there is an instance of the same name in the submitted form
                     $instance = $this->outlet($id);
                     if ($instance instanceof WFWidget) {
                         // walk up looking for parent
                         $parent = $instance->parent();
                         do {
                             if ($parent and $parent instanceof WFForm and $parent->name() == $this->submittedFormName()) {
                                 $parameters[$id] = $this->outlet($id)->value();
                                 break;
                             }
                             if (!is_object($parent)) {
                                 throw new Exception("Error processing parameter overload for parameter id: '{$id}': found widget of same id, but cannot determine the form that it belongs to. Are you sure that widget id: '{$id}' is in a WFForm?");
                             }
                             $parent = $parent->parent();
                         } while ($parent);
                     }
                 }
             }
         }
     } else {
         WFLog::log("Skipping Parameterization for {$pageName}", WFLog::TRACE_LOG);
         if (count($parameterList) > 0) {
             // NULL-ify all params
             for ($i = 0; $i < count($parameterList); $i++) {
                 $parameters[$parameterList[$i]] = NULL;
             }
         }
     }
     // save completed parameters
     $this->parameters = $parameters;
     // inform delegate that params are ready
     $this->parametersDidLoad();
     // parametersDidLoad may have affected the arrayControllers
     $this->createDynamicWidgets();
     // restore UI state AGAIN so that any controls created dynamically can update their values based on the UI state.
     if ($this->isRequestPage()) {
         $this->restoreState();
     }
     // now that the UI is set up (instantiated), it's time to propagate the values from widgets to bound objects if this is the requestPage!
     // then, once the values are propagated, we should call the action handler for the current event, if there is one.
     if ($this->isRequestPage()) {
         // let delegate know that we're about to push bindings - this is effectively a statement of "we're in 'postback', deal with it if you must"
         $this->willPushBindings();
         // willPushBindings may have affected the arrayControllers
         $this->createDynamicWidgets();
         // push values of bound properties back to their bound objects
         $this->module->requestPage()->pushBindings();
         // Determine action: do we need to call the noAction handler?
         $rpc = NULL;
         if ($this->hasSubmittedForm()) {
             // look for action in the form data
             $rpc = WFRPC::rpcFromRequest($this->module()->invocation()->invocationPath());
             if (!$rpc) {
                 // look for the submit button;
                 // look up the instance ID for the specified action... look for "action|<actionOutletID>" in $_REQUEST...
                 // but need to skip the _x and _y fields submitted with image submit buttons
                 $actionOutletID = NULL;
                 foreach ($_REQUEST as $name => $value) {
                     if (strncmp("action|", $name, 7) == 0 and !in_array(substr($name, -2, 2), array('_x', '_y'))) {
                         list(, $actionOutletID) = explode('|', $name);
                         break;
                     }
                 }
                 // if there is no button found in the parameters, we ask the WFForm what the default submit button is
                 if (!$actionOutletID) {
                     $form = $this->outlet($this->submittedFormName());
                     $actionOutletID = $form->defaultSubmitID();
                     WFLog::log("Form submitted, but no action button detected. Using default button: {$actionOutletID}", WFLog::TRACE_LOG);
                 }
                 // call the ACTION handler for the page, if there is an action.
                 if ($actionOutletID) {
                     try {
                         $action = $this->outlet($actionOutletID)->submitAction();
                         $action->rpc()->setArguments(array($actionOutletID, 'click'));
                         $rpc = $action->rpc();
                     } catch (Exception $e) {
                         throw new WFException("Could not find form button (outlet) for current action: {$actionOutletID}. Make sure that you don't have nested forms!");
                     }
                 } else {
                     WFLog::log("No action occurred (no action specified in form data)", WFLog::WARN_LOG);
                 }
             }
         } else {
             // look for action in Params
             // new-school WFAction stuff;
             $rpc = WFRPC::rpcFromRequest($this->module()->invocation()->invocationPath());
         }
         // deal with action
         if ($rpc) {
             $shouldRun = false;
             if ($this->hasSubmittedForm()) {
                 if ($rpc->runsIfInvalid() or !$rpc->runsIfInvalid() and $this->formIsValid()) {
                     if ($rpc->runsIfInvalid()) {
                         $this->setIgnoreErrors(true);
                         // runsIfInvalid by default implies ignoreErrors
                     }
                     $shouldRun = true;
                 } else {
                     if ($rpc->isAjax()) {
                         $this->sendPageErrorsOverAjax();
                     }
                 }
                 if ($shouldRun === false) {
                     $this->willNotRunAction();
                 }
             } else {
                 $shouldRun = true;
             }
             if ($shouldRun) {
                 try {
                     $rpc->execute($this);
                 } catch (WFErrorCollection $e) {
                     // Automagically map all errors for keys to widgets with the same ID
                     // note that propagateErrorsForKey* functions may prune errors
                     // from the original WFErrorCollection before we get here.
                     foreach ($this->instances as $id => $obj) {
                         if ($e->hasErrorsForKey($id)) {
                             $this->propagateErrorsForKeyToWidget($e, $id, $obj, true);
                         }
                     }
                     // add all remaining errors to the general error display
                     $this->addErrors($e->allErrors());
                 }
                 if ($rpc->isAjax() and count($this->errors())) {
                     $this->sendPageErrorsOverAjax();
                 }
             }
         } else {
             $this->noAction();
         }
         // action/noAction may have affecting the arrayControllers
         $this->createDynamicWidgets();
     }
 }
Exemplo n.º 2
0
 /**
  * Function to load data structures via fixtures. Fixtures are YAML files that specify name-value pairs for objects.
  *
  * @param array An array of paths to YAML fixture files.
  * @param string The method to call on all top-level objects declared in the YAML file. Defaults to NULL (no call). Useful for calling "save" method to persist fixtures to a DB.
  * @param array An array of arguments to pass into the save method. Useful for Dependency Injection of db connection for instance.
  * @return array An array containing all created objects, as an associative array. NOTE that if you load multiple files with this call and you have the same "name" for different objects,
  *               they will stomp each other and you aren't guaranteed which one you'll get.
  * @throws object WFException
  */
 public function loadFiles($files, $saveMethod = NULL, $saveMethodArgs = array())
 {
     $allCreatedObjects = array();
     // load the fixtures data & process
     foreach ($files as $file) {
         $pathParts = pathinfo($file);
         switch (strtolower($pathParts['extension'])) {
             case 'yaml':
                 $newObjs = $this->processObjectList(WFYaml::load($file));
                 $allCreatedObjects = array_merge($newObjs, $allCreatedObjects);
                 break;
             default:
                 throw new WFException("No fixture support for files of type {$pathParts['extension']}.");
         }
         if ($saveMethod) {
             foreach ($newObjs as $o) {
                 try {
                     call_user_func_array(array($o, $saveMethod), $saveMethodArgs);
                 } catch (Exception $e) {
                     throw new WFException("Uncaught Exception (" . get_class($e) . ") saving object: " . $o . "\n" . $e->getMessage());
                 }
             }
         }
     }
     return $allCreatedObjects;
 }
Exemplo n.º 3
0
 /**
  * Prepare any declared shared instances for the module.
  *
  * Shared Instances are objects that not WFView subclasses. Only WFView subclasses may be instantiated in the <pageName>.instances files.
  * The Shared Instances mechanism is used to instantiate any other objects that you want to use for your pages. Usually, these are things
  * like ObjectControllers or Formatters, which are typically "shared" across multiple pages. The Shared Instances mechanism makes it
  * easy to instantiate and configure the properties of objects without coding, and have these objects accessible for bindings or properties.
  * Of course, you can instantiate objects yourself and use them programmatically. This is just a best-practice for a common situation.
  *
  * The shared instances mechanism simply looks for a shared.instances and a shared.config file in your module's directory. The shared.instances
  * file should simply have a var $__instances that is an associative array of 'unique id' => 'className'. For each declared instance, the
  * module's instance var $this->$uniqueID will be set to a new instance of "className".
  *
  * <code>
  *   $__instances = array(
  *       'instanceID' => 'WFObjectController',
  *       'instanceID2' => 'WFUnixDateFormatter'
  *   );
  * </code>
  *
  * To bind to a shared instance (or for that matter any object that's an instance var of the module), set the instanceID to "#module#,
  * leave the controllerKey blank, and set the modelKeyPath to "<instanceVarName>.rest.of.key.path".
  *
  * To use a shared instance as a property, .................... NOT YET IMPLEMENTED.
  *
  *
  * @todo Allow properties of page.config files to use shared instances.
  */
 function prepareSharedInstances()
 {
     $app = WFWebApplication::sharedWebApplication();
     $modDir = $this->invocation()->modulesDir();
     $moduleInfo = new ReflectionObject($this);
     $yamlFile = $modDir . '/' . $this->invocation->modulePath() . '/shared.yaml';
     if (file_exists($yamlFile)) {
         $yamlConfig = WFYaml::load($yamlFile);
         foreach ($yamlConfig as $id => $instInfo) {
             try {
                 $moduleInfo->getProperty($id);
             } catch (Exception $e) {
                 WFLog::log("shared.yaml:: Module '" . get_class($this) . "' does not have property '{$id}' declared.", WFLog::WARN_LOG);
             }
             // instantiate, keep reference in shared instances
             WFLog::log("instantiating shared instance id '{$id}'", WFLog::TRACE_LOG);
             $this->__sharedInstances[$id] = $this->{$id} = new $instInfo['class']();
             WFLog::log("loading config for shared instance id '{$id}'", WFLog::TRACE_LOG);
             // get the instance to apply config to
             if (!isset($this->{$id})) {
                 throw new WFException("Couldn't find shared instance with ID '{$id}' to configure.");
             }
             $configObject = $this->{$id};
             // atrributes
             if (isset($instInfo['properties'])) {
                 foreach ($instInfo['properties'] as $keyPath => $value) {
                     switch (gettype($value)) {
                         case "boolean":
                         case "integer":
                         case "double":
                         case "string":
                         case "NULL":
                             // these are all OK, fall through
                             break;
                         default:
                             throw new WFException("Config value for shared instance id::property '{$id}::{$keyPath}' is not a vaild type (" . gettype($value) . "). Only boolean, integer, double, string, or NULL allowed.");
                             break;
                     }
                     WFLog::log("SharedConfig:: Setting '{$id}' property, {$keyPath} => {$value}", WFLog::TRACE_LOG);
                     $configObject->setValueForKeyPath($value, $keyPath);
                 }
             }
         }
     } else {
         $instancesFile = $modDir . '/' . $this->invocation->modulePath() . '/shared.instances';
         $configFile = $modDir . '/' . $this->invocation->modulePath() . '/shared.config';
         if (file_exists($instancesFile)) {
             include $instancesFile;
             foreach ($__instances as $id => $class) {
                 // enforce that the instance variable exists
                 try {
                     $moduleInfo->getProperty($id);
                 } catch (Exception $e) {
                     WFLog::log("shared.instances:: Module '" . get_class($this) . "' does not have property '{$id}' declared.", WFLog::WARN_LOG);
                 }
                 // instantiate, keep reference in shared instances
                 $this->__sharedInstances[$id] = $this->{$id} = new $class();
             }
             // configure the new instances
             $this->loadConfigPHP($configFile);
         }
     }
     // call the sharedInstancesDidLoad() callback
     $this->sharedInstancesDidLoad();
 }
Exemplo n.º 4
0
 public function buildModel($adapter, $configFile, $buildEntities)
 {
     // bootstrap
     $builderClass = 'WFModelBuilder' . $adapter;
     $this->builder = new $builderClass();
     $this->builder->setup();
     foreach ($buildEntities as $entity) {
         $this->buildEntity($entity);
     }
     if (file_exists($configFile)) {
         // READ CONFIG - read a config YAML file and "override" settings in various entities, such as descriptiveColumnName, or cardinality:
         $config = WFYaml::load($configFile);
         // Blog:
         //   descriptiveColumnName: name
         //   relationships:
         //     BlogPreferences:
         //       minCount: 0
         //       maxCount: NULL
         //       isExtension: true
         // apply config
         foreach ($config as $entityName => $entityConfig) {
             try {
                 $entity = $this->getEntity($entityName);
             } catch (WFException $e) {
                 print "WARNING: Entity {$entityName} not loaded...\n";
                 continue;
             }
             foreach ($entityConfig as $key => $config) {
                 switch ($key) {
                     case 'relationships':
                         foreach ($config as $relationshipName => $relationshipConfig) {
                             $rel = $entity->getRelationship($relationshipName);
                             if (!$rel) {
                                 print "WARNING: Relationship: {$relationshipName} of Entity {$entityName} not loaded...\n";
                                 continue;
                             }
                             foreach ($relationshipConfig as $key => $value) {
                                 if ($key === 'inverseRelationship') {
                                     list($entityName, $relName) = explode('.', $value);
                                     if (!$entityName or !$relName) {
                                         throw new WFException("inverseRelationship format must be <entityName>.<relationshipName>");
                                     }
                                     if (!$this->getEntity($entityName)) {
                                         $invEntity = new WFModelEntity();
                                         $invEntity->setValueForKey($entityName, 'name');
                                         $this->builder->buildEntityModel($invEntity);
                                         $this->addEntity($invEntity);
                                     }
                                     $rel->setInverseRelationship($this->getEntity($entityName)->getRelationship('relName'));
                                 } else {
                                     $rel->setValueForKey($value, $key);
                                 }
                             }
                         }
                         break;
                     case 'properties':
                         foreach ($config as $propertyName => $propertyConfig) {
                             $property = $entity->getProperty($propertyName);
                             if (!$property) {
                                 print "WARNING: Property: {$propertyName} of Entity {$entityName} not loaded...\n";
                                 continue;
                             }
                             foreach ($propertyConfig as $key => $value) {
                                 switch ($key) {
                                     case 'type':
                                         $value = eval("return {$value};");
                                         break;
                                 }
                                 $property->setValueForKey($value, $key);
                             }
                         }
                         break;
                     default:
                         $entity->setValueForKey($config, $key);
                         break;
                 }
             }
         }
     }
 }