/** * STAGE 3: Choose, create, and optionally update models using business logic. * STAGE 4: Apply business logic to create a presentation model for the view. */ public function editAction() { $config = Zend_Registry::get('config'); // application-wide configuration ini if ($config->db->modelSet === 'pdo') { $this->renderToSegment('body', 'addedInSectionDb'); // initiate STAGE 6 in the view template } ///////////////////////////// // ==> SECTION: db <== // ZFDemoModel_Topics provides static methods that auto-instantiate and manage the model $topics = ZFDemoModel_Topics::getDomainModel(); $deletedRows = array(); // list of deleted topics indexed by topic id $visible = array(); // visibility settings indexed by topic id $changedRows = array(); // list of altered topic rows ZFDemo_Log::log(_('INFO BEGIN editAction()')); try { // iterate over the form variable, queing actions for each change requested foreach ($_POST as $var => $val) { if (!strncmp($var, 'delete', 6)) { $id = intval(substr($var, 6)); if (isset($topics[$id])) { $deletedRows[$id] = $topics[$id]; ZFDemo_Log::log(_('INFO deleting topic id #%1$d', $id)); } } elseif (!strncmp($var, 'visible', 7)) { $id = intval(substr($var, 7)); if (isset($topics[$id])) { $visible[$id] = 1; // ask Zend_Db_Table_Row to delete itself } } } // for every topic, make sure visibility setting matches the selections submitted via the form foreach ($topics as $id => $row) { if (!isset($deletedRows[$id])) { // don't update a row that will be deleted if (!isset($visible[$id])) { // the checkbox for visibility of this topic was not checked $visible[$id] = 0; // web interface defaults to checking visible topics, so unchecked means not visible } if ($row->is_visible != $visible[$id]) { ZFDemo_Log::log(_('INFO topic id %1$d visibility set to %2$d', $id, $visible[$id])); $row->is_visible = $visible[$id]; $changedRows[$id] = $row; // add this row to the list of changed rows } } } // now commit changes to DB $registry = Zend_Registry::getInstance(); if (!$registry['config']->db->transactions) { // note: query aggregation would help performance (reader excercise) foreach ($deletedRows as $row) { $row->delete(); // now do all the deletes at once } foreach ($changedRows as $row) { $row->save(); // now synchronize all the modified rows with the table } } else { // table type supports transactions $db = $registry['db']; $db->beginTransaction(); try { // note: query aggregation would help performance (reader excercise) foreach ($deletedRows as $row) { $row->delete(); // now do all the deletes at once } foreach ($changedRows as $row) { $row->save(); // now synchronize all the modified rows with the table } $db->commit(); } catch (Exception $exception) { $db->rollBack(); // re-throw the exception throw $exception; // allow normal processing by the preDispatch() of the plugin in bootstrap.php } } } catch (Exception $exception) { ZFDemo_Log::log(_('ERROR END editAction() - failed')); throw $exception; // resume normal processing of the exception } ZFDemo_Log::log(_('NOTICE END editAction() - succeeded')); // Controller = 'index', Action = 'index' $this->setRedirectCode(303); // PRG pattern via http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html $this->_redirect('forum/admin_topics'); }
/** * Accumulate log messages, but also append them to a running log file for easy viewing. */ public static function log($msg, $before = null) { static $flushed = false; if ($before) { self::$_log = "{$msg}\n" . self::$_log; } else { self::$_log .= "{$msg}\n"; } $registry = Zend_Registry::getInstance(); // performance is not an issue for this demo, so just sync to disk everytime if (isset($registry['config'])) { if ($flushed) { file_put_contents($registry['config']->log, "{$msg}\n", FILE_APPEND); } else { file_put_contents($registry['config']->log, self::$_log); $flushed = true; } } }
/** * Initialize the module by mapping any authentication id into the forum system's user table, * and performing access control on forum controllers and actions. * @return Zend_Config|null Zend_Config should contain keys mapping to module/controller/action for a reroute */ public static function moduleAuth(Zend_Config $config, Zend_Controller_Request_Abstract $request) { // access the session namespace 'auth' to see if user is authenticated $authSpace = new Zend_Session_Namespace('auth'); if (!empty($authSpace->authenticationId)) { // try mapping the application's concept of authentication id to this module's authorization id self::authenticationId2authorizationId($authSpace->authenticationId); } /* Variant of "Authenticate #2" ( see "Authenticate Where?" http://framework.zend.com/wiki/x/fUw ) * This point in the flow of execution also enables us to examine the module's * interpretation of the authentication id. * If anonymous use has been disabled in this module's settings in "modules.ini", * and the user was not recognized by this module's authenticationId2authorizationId(), then */ if (empty($config->allowAnonymousUse) && empty(self::$_authorizationId)) { return $config->authenticate; // allow front controller plugin to redirect in preDispatch() // Reader excercise: redirect to something with a "prettier" explanation message } if (!$config->acl) { // ACL has been enabled in "modules.ini" for this module return; } ///////////////////////////// // ==> SECTION: acl <== require_once 'Zend/Acl.php'; require_once 'Zend/Acl/Role.php'; // Use the default ZF "role" class require_once 'Zend/Acl/Resource.php'; // Use the default ZF "resource" class ///////////////////////////// // ==> SECTION: perf <== // Create a caching system from a frontend and backend system using options from "modules.ini" $back = $config->cache->back->toArray(); $back['cacheDir'] = Zend_Registry::get('temporaryDir') . DIRECTORY_SEPARATOR . 'modules'; $cache = Zend_Cache::factory('Core', 'File', $config->cache->front->toArray(), $back); // BEWARE!!! If you change something below, *delete* the cache file in temporary/Zend if (!($acl = $cache->load(__CLASS__))) { ///////////////////////////// // ==> SECTION: acl <== // Reader excercise: load the following from a config file $acl = new Zend_Acl(); $acl->add(new Zend_Acl_Resource('posts')); // add() is really addResource() $acl->add(new Zend_Acl_Resource('topics')); // add resource 'topics' to our ACL $acl->add(new Zend_Acl_Resource('submissions')); // add resource 'topics' to our ACL $acl->addRole(new Zend_Acl_Role('anonymous')); // 'anonymous' role does not inherit access controls from any role $acl->allow('anonymous', 'posts', 'display'); // grant "display" privileges on posts $acl->allow('anonymous', 'topics', 'display'); // grant "display" privileges on topics // also works: // $acl->allow('anonymous', array('posts', 'topics'), 'display'); // 'member' role inherits from 'anonymous' role $acl->addRole(new Zend_Acl_Role('member'), 'anonymous'); // grant 'member' role ability to add/submit posts $acl->allow('member', 'submissions', 'post'); // 'moderator' role inherits from 'member' role $acl->addRole(new Zend_Acl_Role('moderator'), 'member'); // grant 'moderator' role ability to hide and delete posts $acl->allow('moderator', 'posts', array('visible', 'delete')); $acl->allow('moderator', null, 'display'); // moderators can display everything $acl->addRole(new Zend_Acl_Role('admin')); // inherit no constraints $acl->allow('admin'); // grant all privileges on all resources to 'admin' role $acl->add(new Zend_Acl_Resource('sorry')); // add resource for the "sorry" controller $acl->allow(null, 'sorry', 'display'); // grant display privilege for "sorry" messages to all roles $acl->allow(array('member', 'moderator', 'admin'), 'submissions', null); // allow them to add posts ///////////////////////////// // ==> SECTION: perf <== $cache->save($acl, __CLASS__); } else { // $acl is automatically unserialized from the cache hit retrieved from the cache } ///////////////////////////// // ==> SECTION: acl <== // NORMALIZE information needed for ACL check $controller = $request->getControllerName(); $action = $request->getActionName(); // map default privilege to correct privilege name if ($action === 'index') { $action = 'display'; } // map default resource to correct resource name if ($controller === 'index') { $controller = 'posts'; } // determine the role of the current user $role = self::$_user ? self::$_user->role : 'anonymous'; // if the controller has been defined as a resource in the ACL, then check the ACL if ($acl->has($controller)) { $isAllowed = $acl->isAllowed($role, $controller, $action); } else { // allow access by admins for controllers not registered in the ACL $isAllowed = $role === 'admin' ? true : false; } ZFDemo_Log::log("acl->isAllowed(role: {$role}, resource: {$controller}, permission: {$action}) = " . ($isAllowed ? 'true' : 'false') . "\n"); if (!$isAllowed) { // if the request was not allowed, then // redirect to this module's "unauthorized" setting in "modules.ini" return $config->unauthorized; // front controller plugin will perform redirection in preDispatch() } }
/** * Modules are semi-standalone, encapsulated "mini" applications. * Thus, there is a need for a global module initialization process, * for the dispatched module to provide shared initializations for * all controllers within this module. */ public function preDispatch(Zend_Controller_Request_Abstract $request) { $frontController = Zend_Controller_Front::getInstance(); $moduleName = $request->getModuleName(); $registry = Zend_Registry::getInstance(); $moduleInit = $registry['appDir'] . $moduleName . DIRECTORY_SEPARATOR . $moduleName . '.php'; $modulesIni = $registry['configDir'] . 'modules.ini'; // If a module has an initialization / authorization component if (is_readable($moduleInit)) { include_once $moduleInit; if (!isset($registry['config']->cache)) { // if caching of module ini files is not enabled, just load it now $config = new Zend_Config_Ini($modulesIni, $moduleName); } ///////////////////////////// // ==> SECTION: auth <== $rerouteTo = null; /* Allow modules.ini to disable anonymous access to individual modules. * Authenticate #2 ( see "Authenticate Where?" http://framework.zend.com/wiki/x/fUw ) */ if (empty($config->allowAnonymousUse)) { if (empty($registry['authenticationId'])) { // if not already authenticated $rerouteTo = $config->authenticate; } } if ($rerouteTo === null) { // Access control could also be selectively applied to entire modules, instead of inside the module: $rerouteTo = call_user_func(array('ZFModule_' . ucfirst($moduleName), 'moduleAuth'), $config, $request); } if ($rerouteTo) { if (--$this->maxDispatches > 0) { if ($rerouteTo == $this->didRerouteTo) { $msg = _('ERROR Looping detected in preDispatch().') . $request->getRequestUri(); ZFDemo_Log::log($msg); throw new ZFDemo_Exception_Reroute($msg, 500); } ZFDemo::reroute($request, $rerouteTo, $frontController, null, true); $this->didRerouteTo = $rerouteTo; } else { $msg = _('ERROR Too many reroutes in preDispatch(). Looping?') . $request->getRequestUri(); ZFDemo_Log::log($msg); throw new ZFDemo_Exception_Reroute($msg, 500); } } } // dynamic configurations, DRY, O(1) with respect to number of modules // http://framework.zend.com/issues/browse/ZF-1125 // $frontController->setControllerDirectory(array($moduleName => Zend_Registry::get('appDir') // . $moduleName . $ds . 'controllers')); return; }
/** * logoff, and show logon page */ public function logoutAction() { ZFDemo_Log::log('logout'); // STAGE 3: Choose, create, and optionally update models using business logic. Zend_Session::namespaceUnset('auth'); // remove the authentication id and information Zend_Registry::set('authenticationId', 0); // really need observer pattern // STAGE 4: Apply business logic to create a presentation model for the view. // STAGE 5: Choose view template and submit presentation model to view template for rendering. $this->_forward('index'); // possibly valid topic, so try to show its posts }