Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • None
  • system
    • base
    • caching
    • console
    • db
      • ar
      • schema
    • validators
    • web
      • actions
      • auth
      • helpers
      • widgets
        • captcha
        • pagers
  • zii
    • widgets
      • grid

Classes

  • AdminController
  • Api2Controller
  • ApiController
  • BugReportsController
  • CommonSiteControllerBehavior
  • ProfileController
  • RelationshipsController
  • SearchController
  • SiteController
  • StudioController
  • TemplatesController
  • TopicsController
  • x2base
  • X2Controller
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: 
   3: /*****************************************************************************************
   4:  * X2Engine Open Source Edition is a customer relationship management program developed by
   5:  * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
   6:  * 
   7:  * This program is free software; you can redistribute it and/or modify it under
   8:  * the terms of the GNU Affero General Public License version 3 as published by the
   9:  * Free Software Foundation with the addition of the following permission added
  10:  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  11:  * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
  12:  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  13:  * 
  14:  * This program is distributed in the hope that it will be useful, but WITHOUT
  15:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16:  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
  17:  * details.
  18:  * 
  19:  * You should have received a copy of the GNU Affero General Public License along with
  20:  * this program; if not, see http://www.gnu.org/licenses or write to the Free
  21:  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  22:  * 02110-1301 USA.
  23:  * 
  24:  * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
  25:  * California 95067, USA. or at email address contact@x2engine.com.
  26:  * 
  27:  * The interactive user interfaces in modified source and object code versions
  28:  * of this program must display Appropriate Legal Notices, as required under
  29:  * Section 5 of the GNU Affero General Public License version 3.
  30:  * 
  31:  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  32:  * these Appropriate Legal Notices must retain the display of the "Powered by
  33:  * X2Engine" logo. If the display of the logo is not reasonably feasible for
  34:  * technical reasons, the Appropriate Legal Notices must display the words
  35:  * "Powered by X2Engine".
  36:  *****************************************************************************************/
  37: 
  38: Yii::import('application.components.util.*');
  39: 
  40: /**
  41:  * Administrative, app-wide configuration actions.
  42:  *
  43:  * @package application.controllers
  44:  * @property boolean $noRemoteAccess (Read-only) true indicates there's no way to automatically retrieve files.
  45:  * @property string $exportFile (read-only) path to the data export file
  46:  * @author Jake Houser <jake@x2engine.com>
  47:  */
  48: class AdminController extends X2Controller {
  49: 
  50:     public $modelClass = 'Admin';
  51:     public $portlets = array();
  52:     public $layout = '//layouts/column1';
  53: 
  54:     /**
  55:      * @var bool $noBackdrop If true, then the content will not have a backdrop
  56:      */
  57:     public $noBackdrop = false;
  58: 
  59:     /**
  60:      * Behavior classes used by AdminController
  61:      * @var array
  62:      */
  63:     public static $behaviorClasses = array(
  64:         'LeadRoutingBehavior',
  65:         'UpdaterBehavior',
  66:         'CommonControllerBehavior',
  67:         'ImportExportBehavior',
  68:         
  69:     );
  70: 
  71:     /**
  72:      * Extraneous properties for individual behaviors
  73:      * @var array
  74:      */
  75:     public static $behaviorProperties = array('UpdaterBehavior' => array('isConsole' => false));
  76: 
  77:     /**
  78:      * Miscellaneous component classes that the controller (or its behaviors)
  79:      * depend on, but that aren't directly used by it as behaviors.
  80:      * @var type
  81:      */
  82:     public static $dependencies = array(
  83:         'util/FileUtil', 'util/EncryptUtil', 'util/ResponseUtil', 'ResponseBehavior',
  84:         'views/requirements');
  85: 
  86:     /**
  87:      * Stores value of {@link $noRemoteAccess}
  88:      * @var boolean
  89:      */
  90:     private $_noRemoteAccess;
  91:     private $_behaviors;
  92:     private $_exportFile;
  93: 
  94:     /**
  95:      * A list of actions to include.
  96:      *
  97:      * This method specifies which actions are defined elsewhere but used here.
  98:      * These actions are pro code that are included in the pro version of the software.
  99:      *
 100:      * @return array An array of actions to include.
 101:      */
 102:     public function actions() {
 103:         return array_merge($this->webUpdaterActions, array(
 104:             'ajaxGetModelAutocomplete' => array(
 105:                 'class' => 'application.components.actions.AjaxGetModelAutocompleteAction',
 106:             ),
 107:             
 108:             'viewLog' => array(
 109:                 'class' => 'LogViewerAction',
 110:             ),
 111:         ));
 112:     }
 113: 
 114:     /**
 115:      * @deprecated
 116:      * This is mostly a developer function used for viewing information about
 117:      * translation files.
 118:      *
 119:      * This method will find a list of messages which have an entry in the translation
 120:      * files but do not have corresponding translations. Since the advent of the
 121:      * translation automation feature, this method should be largely unnecessary.
 122:      */
 123:     public function actionCalculateMissingTranslations() {
 124:         $untranslated = array();
 125:         $languages = scandir('protected/messages');
 126:         foreach ($languages as $lang) {
 127:             if (!in_array($lang, array('template', '.', '..'))) {
 128:                 $untranslated[$lang] = 0;
 129:                 $files = scandir('protected/messages/' . $lang);
 130:                 foreach ($files as $file) {
 131:                     if ($file != '.' && $file != '..') {
 132:                         $translations = array_values(include('protected/messages/' . $lang . '/' . $file));
 133:                         foreach ($translations as $message) {
 134:                             if (empty($message)) {
 135:                                 $untranslated[$lang] ++;
 136:                             }
 137:                         }
 138:                     }
 139:                 }
 140:             }
 141:         }
 142:         /**/printR($untranslated);
 143:     }
 144: 
 145:     /**
 146:      * @deprecated
 147:      * Another function for analyzing the translation files.
 148:      *
 149:      * This method will display the "redundancy" of translation files. That is to say,
 150:      * if a word is contained in two separate files, that word is considered
 151:      * redundant and could be refactored into the "common.php" message file.
 152:      * As of the advent of the translation automation function, redundancy
 153:      * cleanup is a part of that process and this method should be unnecessary.
 154:      */
 155:     public function actionCalculateTranslationRedundancy() {
 156:         $max = array('file1' => 'null', 'file2' => 'null', 'redundancy' => 0);
 157:         $files = scandir('protected/messages/template');
 158:         $duplicates = array();
 159:         $languageList = array();
 160:         $totalWords = array();
 161:         $untranslated = 0;
 162:         foreach ($files as $file) {
 163:             if ($file != '.' && $file != '..') {
 164:                 $languageList[$file] = array_keys(include("protected/messages/template/$file"));
 165:             }
 166:         }
 167:         $keys = array_keys($languageList);
 168:         for ($i = 0; $i < count($languageList); $i++) {
 169:             $totalWords = array_merge($totalWords, $languageList[$keys[$i]]);
 170:             for ($j = $i + 1; $j < count($languageList); $j++) {
 171:                 $intersect = array_intersect($languageList[$keys[$i]], $languageList[$keys[$j]]);
 172:                 if (!empty($intersect)) {
 173:                     $duplicates = array_unique(array_merge($duplicates, $intersect));
 174:                     /**/printR($intersect);
 175:                     $unique = count($languageList[$keys[$i]]) + count($languageList[$keys[$j]]) - count($intersect);
 176:                     $redundancy = round(count($intersect) / $unique * 100, 2);
 177:                     echo "Between " . $keys[$i] . " and " . $keys[$j] . ": " . $redundancy . "% items identical.<br />";
 178:                     if ($redundancy > $max['redundancy']) {
 179:                         $max['file1'] = $keys[$i];
 180:                         $max['file2'] = $keys[$j];
 181:                         $max['redundancy'] = $redundancy;
 182:                     }
 183:                 }
 184:             }
 185:         }
 186:         echo "<br>The most redundant files are " . $max['file1'] . " and " . $max['file2'] . " with a redundancy of " . $max['redundancy'] . "%<br><br>";
 187:         echo "There are " . count($duplicates) . " entries which occur more than once.<br><br>";
 188:         echo "There are " . count($totalWords) . " entries in the translation files.";
 189:     }
 190: 
 191:     private static function findMissingPermissions() {
 192:         $controllers = array(
 193:             'AdminController' => 'application.controllers.AdminController',
 194:             'StudioController' => 'application.controllers.StudioController',
 195:             'AccountsController' => 'application.modules.accounts.controllers.AccountsController',
 196:             'ActionsController' => 'application.modules.actions.controllers.ActionsController',
 197:             'CalendarController' => 'application.modules.calendar.controllers.CalendarController',
 198:             'ContactsController' => 'application.modules.contacts.controllers.ContactsController',
 199:             'DocsController' => 'application.modules.docs.controllers.DocsController',
 200:             'GroupsController' => 'application.modules.groups.controllers.GroupsController',
 201:             'MarketingController' =>
 202:                 'application.modules.marketing.controllers.MarketingController',
 203:             'MediaController' => 'application.modules.media.controllers.MediaController',
 204:             'OpportunitiesController' =>
 205:                 'application.modules.opportunities.controllers.OpportunitiesController',
 206:             'ProductsController' => 'application.modules.products.controllers.ProductsController',
 207:             'QuotesController' => 'application.modules.quotes.controllers.QuotesController',
 208:             'ServicesController' => 'application.modules.services.controllers.ServicesController',
 209:             'TopicsController' => 'application.modules.topics.controllers.TopicsController',
 210:             'UsersController' => 'application.modules.users.controllers.UsersController',
 211:             'WorkflowController' => 'application.modules.workflow.controllers.WorkflowController',
 212:             'X2LeadsController' => 'application.modules.x2Leads.controllers.X2LeadsController',
 213:             'BugReportsController' =>
 214:                 'application.modules.bugReports.controllers.BugReportsController',
 215:             
 216:         );
 217:         $missingPermissions = array();
 218:         $auth = Yii::app()->authManager;
 219:         foreach ($controllers as $class => $controller) {
 220:             Yii::import($controller);
 221:             $methods = get_class_methods($class); // Grab all functions from the controller
 222:             $arr = explode('Controller', $class);
 223:             $name = $arr[0];
 224:             if (is_array($methods)) {
 225:                 foreach ($methods as $method) {
 226:                     // Only look for methods that start with "action"
 227:                     if (strpos($method, 'action') === 0 && $method != 'actions') {
 228:                         $method = $name . substr($method, 6);
 229:                         $authItem = $auth->getAuthItem($method);
 230:                         // We can't find a permission, add it to the list of missing ones
 231:                         if (is_null($authItem))
 232:                             $missingPermissions[] = $method;
 233:                     }
 234:                 }
 235:             }
 236:             if (preg_match('/modules/', $controller)) {
 237:                 $moduleName = preg_replace(
 238:                     '/application.modules.([^.]+)\..*/', '$1', $controller);
 239:                 $moduleClassName = ucfirst($moduleName) . 'Module';
 240:                 Yii::import(preg_replace('/controller.*/', '*', $controller));
 241:                 $controller = new $class('', new $moduleClassName($moduleName, null));
 242:                 $actions = $controller->actions();
 243:                 foreach ($actions as $actionName => $params) {
 244:                     $method = $name . ucfirst($actionName);
 245:                     $authItem = $auth->getAuthItem($method);
 246:                     if (is_null($authItem))
 247:                         $missingPermissions[] = $method;
 248:                 }
 249:             } else {
 250:                 Yii::import(preg_replace('/controller.*/', '*', $controller));
 251:                 $controller = new $class('');
 252:                 $actions = $controller->actions();
 253:                 foreach ($actions as $actionName => $params) {
 254:                     $method = $name . ucfirst($actionName);
 255:                     $authItem = $auth->getAuthItem($method);
 256:                     if (is_null($authItem))
 257:                         $missingPermissions[] = $method;
 258:                 }
 259:             }
 260:         }
 261:         return $missingPermissions;
 262:     }
 263: 
 264:     /**
 265:      * A function to print a list of actions which are present in controller files
 266:      * but no corresponding permission exists in the database.
 267:      *
 268:      * This function should ideally be run before each release as a developer tool
 269:      * to view what permissions are missing from the software. Any controller action
 270:      * with no permission associated with it is assumed to be allowed so this is
 271:      * a good way to look for potential security holes. Please note that all
 272:      * relevant controllers must be specified by name in the array at the top
 273:      * of the function.
 274:      */
 275:     public function actionFindMissingPermissions() {
 276:         /**/printR(self::findMissingPermissions());
 277:     }
 278: 
 279:     
 280: 
 281: // Used to manually test updater file copy in Windows
 282: //    public static function caseSensitiveCopyTest () {
 283: //        $ds = DIRECTORY_SEPARATOR;
 284: //        $ube = new CComponent();
 285: //        $properties = array ();
 286: //        $ubconfig = array_merge(array(
 287: //            'class' => 'UpdaterBehavior',
 288: //            'isConsole' => true,
 289: //            'noHalt' => true,
 290: //        ),$properties);
 291: //        $ube->attachBehavior('UpdaterBehavior', $ubconfig);
 292: //        $source = $ube->webroot.'/protected/tests/data/UpdaterBehaviorTest/source/a.js';
 293: //        $target = $ube->webroot.'/protected/tests/data/UpdaterBehaviorTest/app/a.js';
 294: //
 295: //        $sourcePath = FileUtil::relpath($source, '.');
 296: //        $targetPath = FileUtil::relpath($target, '.');
 297: //        FileUtil::ccopy ($source, $target);
 298: //        $ube->removeFiles (array (
 299: //            '/protected/tests/data/UpdaterBehaviorTest/app/A.js'
 300: //        ));
 301: //    }
 302: 
 303:     /**
 304:      * View the main admin menu
 305:      */
 306:     public function actionIndex() {
 307:         //if(isset($_GET['translateMode'])) // Old feature Matthew implemented to better visualize missing translations, no longer used.
 308:         //Yii::app()->session['translate'] = $_GET['translateMode'] == 1;
 309:         $this->render('index');
 310:     }
 311: 
 312:     /**
 313:      * An overridden Yii method that happens before an action.
 314:      *
 315:      * This method handles authorization on an attempt by a user to access an action.
 316:      * A slightly modified version of method is included in X2Base as a behavior.
 317:      *
 318:      * @param string $action A paramter passed by Yii's internal action handling.
 319:      * @return boolean True if the action is allowed to continue, otherwise throw exception.
 320:      * @throws CHttpException Generates a 403 error if authorization fails
 321:      */
 322:     protected function beforeAction($action = null) {
 323:         $this->validateMobileRequest ($action);
 324:         $auth = Yii::app()->authManager;
 325:         $action = ucfirst($this->getId()) . ucfirst($this->getAction()->getId());
 326:         $authItem = $auth->getAuthItem($action);
 327:         // Backwards-compatible way (to make updates safe) of determining if the user has admin rights.
 328:         $imAdmin = false;
 329:         if (Yii::app()->params->hasProperty('isAdmin')) {
 330:             $imAdmin = Yii::app()->params->isAdmin || Yii::app()->user->checkAccess($action);
 331:         } else if (version_compare(Yii::app()->params->version, '2.0') >= 0) {
 332:             $imAdmin = Yii::app()->user->checkAccess('AdminIndex');
 333:         } 
 334:         if ($imAdmin) {
 335:             return true;
 336:         } elseif (Yii::app()->user->isGuest) {
 337:             Yii::app()->user->returnUrl = Yii::app()->request->requestUri;
 338:             $this->redirect($this->createUrl('/site/login'));
 339:         } else {
 340:             throw new CHttpException(403, 'You are not authorized to perform this action.');
 341:         }
 342:     }
 343: 
 344:     /**
 345:      * @deprecated
 346:      * Deprecated method for how to guides.
 347:      *
 348:      * While these guides technically still exist in the software, much more useful
 349:      * and up to date information can be found on the X2Engine website.
 350:      *
 351:      * @param type $guide Which how to guide to access.
 352: 
 353:       public function actionHowTo($guide) {
 354:       if ($guide == 'gii')
 355:       $this->render('howToGii');
 356:       else if ($guide == 'model')
 357:       $this->render('howToModel');
 358:       else
 359:       $this->redirect('index');
 360:       } */
 361: 
 362:     /**
 363:      * Filters to be used by the controller.
 364:      *
 365:      * This method defines which filters the controller will use.  Filters can be
 366:      * built in with Yii or defined in the controller (see {@link AdminController::filterClearCache}).
 367:      * See also Yii documentation for more information on filters.
 368:      *
 369:      * @return array An array consisting of the filters to be used.
 370:      */
 371:     public function filters() {
 372:         // return the filter configuration for this controller, e.g.:
 373:         return array(
 374:             //'accessControl',
 375:             'clearCache',
 376:                 //'clearAuthCache'
 377:         );
 378:     }
 379: 
 380:     /**
 381:      * A list of behaviors for the controller to use.
 382:      *
 383:      * It will download missing files (including classes that aren't behaviors)
 384:      * if any that are defined in {@link $behaviorClasses} are missing from the
 385:      * local filesystem.
 386:      *
 387:      * The reason for all this is that in older versions, the updater utility,
 388:      * when updating itself, will download the latest version of
 389:      * AdminController. This necessitates downloading all of its dependencies,
 390:      * so that AdminController can still run properly, in order to be backwards-
 391:      * compatible.
 392:      *
 393:      * It uses the same form as a typical magic getter method (private storage
 394:      * property, check if it's set first and return) because the method is also
 395:      * called in the override {@link createAction()}
 396:      *
 397:      * {@link LeadRoutingBehavior} is used to consolidate code for lead routing rules.
 398:      * As such, it has been moved to an external file.  This file includes LeadRoutingBehavior
 399:      * or downloads it if the file does not currently exist.  See also Yii documentation
 400:      * for more information on behaviors.
 401:      * {@link UpdaterBehavior} is a centralized, re-usable behavior class for code
 402:      * pertaining to the updater that is agnostic to whether the update is being
 403:      * performed inside of a web request.
 404:      * {@link CommonControllerBehavior} is for methods shared between x2base and Admin controller
 405:      *
 406:      * @return array An array of behaviors to implement.
 407:      */
 408:     public function behaviors() {
 409:         if (!isset($this->behaviors)) {
 410:             $missingClasses = array();
 411:             $behaviors = array();
 412:             $maxTries = 3;
 413:             $GithubUrl = 'https://raw.github.com/X2Engine/X2Engine/master/x2engine/protected';
 414:             $x2planUrl = 'https://x2planet.com/updates/x2engine/protected'; // NOT using UpdaterBehavior.updateServer because that behavior may not yet exist
 415:             $files = array_merge(array_fill_keys(self::$behaviorClasses, 'behavior'), array_fill_keys(self::$dependencies, 'dependency'));
 416:             $tryCurl = in_array(ini_get('allow_url_fopen'), array(0, 'Off', 'off'));
 417:             foreach ($files as $class => $type) {
 418:                 // First try to download from the X2Engine update server...
 419:                 $path = "components/$class.php";
 420:                 $absPath = Yii::app()->basePath . "/$path";
 421:                 if (!file_exists($absPath)) {
 422:                     if (!is_dir(dirname($absPath))) {
 423:                         mkdir(dirname($absPath));
 424:                     }
 425:                     $i = 0;
 426:                     while (!$this->copyRemote("$x2planUrl/$path", $absPath, $tryCurl) && $i < $maxTries) {
 427:                         $i++;
 428:                     }
 429:                     // Try to download the file from Github...
 430:                     if ($i >= $maxTries) {
 431:                         $i = 0;
 432:                         while (!$this->copyRemote("$GithubUrl/$path", $path, $tryCurl) && $i < $maxTries) {
 433:                             $i++;
 434:                         }
 435:                     }
 436:                     // Mark the file as a failed download.
 437:                     if ($i >= $maxTries) {
 438:                         $missingClasses[] = "protected/$path";
 439:                     }
 440:                 }
 441:                 if ($type == 'behavior') {
 442:                     $behaviors[$class] = array(
 443:                         'class' => $class
 444:                     );
 445:                 }
 446:             }
 447: 
 448:             // Display error.
 449:             // Uncomment this next line to test:
 450:             // $missingClasses[] = 'FOO';
 451:             if (count($missingClasses))
 452:                 $this->missingClassesException($missingClasses);
 453: 
 454:             // Add extraneous behavior properties:
 455:             foreach (self::$behaviorProperties as $class => $properties) {
 456:                 foreach ($properties as $name => $value) {
 457:                     $behaviors[$class][$name] = $value;
 458:                 }
 459:             }
 460:             $this->_behaviors = $behaviors;
 461:         }
 462:         return $this->_behaviors;
 463:     }
 464: 
 465:     /**
 466:      * @deprecated
 467:      * Deprecated access control function.
 468:      *
 469:      * This function used to be used to control access roles for actions within
 470:      * the admin tab.  This system has been replaced with Yii's built in RBAC
 471:      * which uses {@link AdminController::beforeAction} to determine permissions.
 472:      */
 473:     public function accessRules() {
 474:         /* return array(
 475:           array('allow', // allow authenticated user to perform 'create' and 'update' actions
 476:           'actions' => array('getRoutingType', 'getRole', 'getWorkflowStages', 'download', 'cleanUp', 'sql', 'getFieldData', 'installUpdate'),
 477:           'users' => array('*'),
 478:           ),
 479:           array('allow', // allow authenticated user to perform 'create' and 'update' actions
 480:           'actions' => array('viewPage', 'getAttributes', 'getDropdown', 'getFieldType'),
 481:           'users' => array('@'),
 482:           ),
 483:           array('allow',
 484:           'actions' => array(
 485:           'index', 'howTo', 'searchContact', 'search', 'toggleAccounts',
 486:           'export', 'import', 'uploadLogo', 'toggleDefaultLogo', 'createModule', 'deleteModule', 'exportModule',
 487:           'importModule', 'toggleSales', 'setTimeout', 'emailSetup', 'googleIntegration', 'setChatPoll',
 488:           'renameModules', 'manageModules', 'createPage', 'contactUs', 'viewChangelog', 'toggleUpdater',
 489:           'translationManager', 'addCriteria', 'deleteCriteria', 'setLeadRouting', 'roundRobinRules',
 490:           'deleteRouting', 'addField', 'removeField', 'customizeFields', 'manageFields', 'editor',
 491:           'createFormLayout', 'deleteFormLayout', 'formVersion', 'dropDownEditor', 'manageDropDowns',
 492:           'deleteDropdown', 'editDropdown', 'roleEditor', 'deleteRole', 'editRole', 'manageRoles',
 493:           'roleException', 'appSettings', 'updater', 'registerModules', 'toggleModule', 'viewLogs', 'delete',
 494:           'tempImportWorkflow', 'workflowSettings', 'testVariables','testRoles'
 495:           ),
 496:           'users' => array('admin'),
 497:           ),
 498:           array('deny',
 499:           'users' => array('*')
 500:           )
 501:           ); */
 502:     }
 503: 
 504:     /**
 505:      * A filter to clear the cache.
 506:      *
 507:      * This method clears the cache whenever the admin controller is accessed.
 508:      * Caching improves performance throughout the app, but will occasionally
 509:      * need to be cleared. Keeping this filter here allows for cleaning up the
 510:      * cache when required.
 511:      *
 512:      * @param type $filterChain The filter chain Yii is currently acting on.
 513:      */
 514:     public function filterClearCache($filterChain) {
 515:         $cache = Yii::app()->cache;
 516:         if (isset($cache))
 517:             $cache->flush();
 518:         $filterChain->run();
 519:     }
 520: 
 521:     /**
 522:      * A filter to clear the authItem cache.
 523:      * @param type $filterChain The filter chain Yii is currently acting on.
 524:      */
 525:     public function filterClearAuthCache($filterChain) {
 526:         // Check for existence of authCache object (for backwards compatibility)
 527:         if (!is_null(Yii::app()->db->getSchema()->getTable('x2_auth_cache'))) {
 528:             if (Yii::app()->hasComponent('authCache')) {
 529:                 $authCache = Yii::app()->authCache;
 530:                 if (isset($authCache))
 531:                     $authCache->clear();
 532:             }
 533:         }
 534:         $filterChain->run();
 535:     }
 536: 
 537:     /**
 538:      * The tag manager page of the administrative section.
 539:      *
 540:      * This page allows for the admin user to view a list of tags and how many
 541:      * records have that tag. From here, the admin can mass delete individual tags
 542:      * or remove all tags.
 543:      */
 544:     public function actionManageTags() {
 545:         $dataProvider = new CActiveDataProvider('Tags', array(
 546:             'criteria' => array(
 547:                 'group' => 'tag'
 548:             ),
 549:             'pagination' => array(
 550:                 'pageSize' => isset($pageSize) ? $pageSize : Profile::getResultsPerPage(),
 551:             ),
 552:         ));
 553: 
 554:         $this->render('manageTags', array(
 555:             'dataProvider' => $dataProvider,
 556:         ));
 557:     }
 558: 
 559:     /**
 560:      * This function is called via AJAX by Manage Tags to remove a tag.
 561:      * @param string $tag The name of the tag to be deleted.
 562:      */
 563:     public function actionDeleteTag($tag) {
 564:         if (is_string ($tag) && strlen ($tag)) {
 565:             if ($tag != 'all') {
 566:                 X2Model::model('Tags')->deleteAllByAttributes(array('tag' => $tag));
 567:             } else {
 568:                 X2Model::model('Tags')->deleteAll();
 569:             }
 570:         }
 571:         $this->redirect('manageTags');
 572:     }
 573: 
 574:     /**
 575:      * An administrative page to see a list of all current sessions. From here,
 576:      * the admin can toggle visible/invisible or end any user session.
 577:      */
 578:     public function actionManageSessions() {
 579:         $dataProvider = new CActiveDataProvider('Session');
 580: 
 581:         $this->render('manageSessions', array(
 582:             'dataProvider' => $dataProvider,
 583:         ));
 584:     }
 585: 
 586:     /**
 587:      * Locates fields for a givin module whose link type field has not yet been
 588:      * restored to the respective dropdown ID
 589:      * @param array $modules Module names
 590:      */
 591:     private function fixupImportedModuleDropdowns($modules) {
 592:         foreach ($modules as $module) {
 593:             $fields = Yii::app()->db->createCommand()
 594:                             ->select('id, linkType')
 595:                             ->from('x2_fields')
 596:                             ->where('type = "dropdown" AND modelName = :model', array(
 597:                                 ':model' => $module
 598:                             ))->queryAll();
 599:             foreach ($fields as $field) {
 600:                 if (!ctype_digit($field['linkType'])) {
 601:                     $dropdownId = Yii::app()->db->createCommand()
 602:                             ->select('id')
 603:                             ->from('x2_dropdowns')
 604:                             ->where('name = :name', array(
 605:                                 ':name' => $field['linkType']
 606:                             ))
 607:                             ->queryScalar();
 608:                     if ($dropdownId) {
 609:                         Yii::app()->db->createCommand()
 610:                                 ->update(
 611:                                     'x2_fields',
 612:                                     array('linkType' => $dropdownId),
 613:                                     'id = :id AND type = "dropdown"',
 614:                                     array(':id' => $field['id'])
 615:                         );
 616:                     }
 617:                 }
 618:             }
 619:         }
 620:     }
 621: 
 622:     
 623: 
 624:     /**
 625:      * An AJAX called function to set a particular session to visible or invisible
 626:      * @param $id The ID of the session to be toggled.
 627:      */
 628:     public function actionToggleSession($id) {
 629:         if (isset($_GET['id'])) {
 630:             $id = $_GET['id'];
 631:             $session = Session::model()->findByPk($id);
 632:             if (isset($session)) {
 633:                 $session->status = !$session->status;
 634:                 $ret = $session->status;
 635:                 if ($session->save()) {
 636:                     echo $ret;
 637:                 }
 638:             }
 639:         }
 640:     }
 641: 
 642:     /**
 643:      * An AJAX called function to allow the admin to forcibly end a session,
 644:      * logging the user out.
 645:      * @param $id The ID of the session.
 646:      */
 647:     public function actionEndSession($id) {
 648:         echo Session::model()->deleteByPk($id);
 649:     }
 650: 
 651:     /**
 652:      * An administrative function to view a historical list of sessions and events
 653:      * associated with them.
 654:      *
 655:      * If the admin has turned on the "Session Logging" feature in the General
 656:      * Settings page, all sessions are logged in the session log here. Specific
 657:      * timestamps for login/logout as well as going visible or invisible are provided
 658:      * here. The admin can also click into a session to load the full history
 659:      * of session related activity for that session.
 660:      */
 661:     public function actionViewSessionLog() {
 662:         $sessionLog = new CActiveDataProvider('SessionLog', array(
 663:             'sort' => array(
 664:                 'defaultOrder' => 'timestamp DESC',
 665:             ),
 666:             'pagination' => array(
 667:                 'pageSize' => Profile::getResultsPerPage()
 668:             )
 669:         ));
 670:         $this->render('viewSessionLog', array(
 671:             'dataProvider' => $sessionLog,
 672:         ));
 673:     }
 674: 
 675:     /**
 676:      * An AJAX called function which will return HTML containing a full history
 677:      * of a particular session, from login to logout.
 678:      * @param $id The ID of the session
 679:      */
 680:     public function actionViewSessionHistory($id) {
 681:         $sessions = X2Model::model('SessionLog')->findAllByAttributes(array('sessionId' => $id));
 682:         $firstTimestamp = 0;
 683:         $lastTimestamp = 0;
 684:         $str = "<table class='items'><thead><tr><th>User</th><th>Status</th><th>Timestamp</th></tr></thead>";
 685:         foreach ($sessions as $session) {
 686:             $str.="<tr>";
 687:             $str.="<td>" . User::getUserLinks($session->user) . "</td>";
 688:             $str.="<td>" . SessionLog::parseStatus($session->status) . "</td>";
 689:             $str.="<td>" . Formatter::formatCompleteDate($session->timestamp) . "</td>";
 690:             $str.="</tr>";
 691:         }
 692:         $str.="</table>";
 693:         echo $str;
 694:     }
 695: 
 696:     /**
 697:      * An administrative function to display a grid of user view data--that is a
 698:      * log of when a user viewed a particular record.
 699:      */
 700:     public function actionUserViewLog() {
 701:         $dataProvider = new CActiveDataProvider('ViewLog', array(
 702:             'sort' => array(
 703:                 'defaultOrder' => 'timestamp DESC',
 704:             ),
 705:             'pagination' => array(
 706:                 'pageSize' => Profile::getResultsPerPage()
 707:             )
 708:         ));
 709:         $this->render('userViewLog', array(
 710:             'dataProvider' => $dataProvider,
 711:         ));
 712:     }
 713: 
 714:     /**
 715:      * Delete all ViewLog entries from the database.
 716:      */
 717:     public function actionClearViewHistory() {
 718:         X2model::model('ViewLog')->deleteAll();
 719:         $this->redirect('userViewLog');
 720:     }
 721: 
 722:     /**
 723:      * Find user for lead routing.
 724:      *
 725:      * This method uses {@link LeadRoutingBehavior} to determine the proper user
 726:      * for lead distribution within the app.  The user is echoed out to allow for
 727:      * access via AJAX request.
 728:      */
 729:     public function actionGetRoutingType() {
 730:         $assignee = $this->getNextAssignee();
 731:         //support original behavior
 732:         if ($assignee == "Anyone")
 733:             $assignee = "";
 734:         echo $assignee;
 735:     }
 736: 
 737:     /**
 738:      * Render/save the Custom Lead Routing Rules
 739:      *
 740:      * This method renders a grid of Custom Round Robin Rules and allows for new
 741:      * rules to be created and saved. These rules are used in conjunction with
 742:      * {@link AdminController::actionGetRoutingType} when the "Custom Round Robin"
 743:      * lead distribution method is chosen.
 744:      */
 745:     public function actionRoundRobinRules() {
 746:         $model = new LeadRouting;
 747:         $users = User::getNames();
 748:         unset($users['Anyone']);
 749:         unset($users['admin']);
 750:         $priorityArray = array();
 751:         for ($i = 1; $i <= LeadRouting::model()->count() + 1; $i++) {
 752:             $priorityArray[$i] = $i;
 753:         }
 754:         $dataProvider = new CActiveDataProvider('LeadRouting', array(
 755:             'criteria' => array(
 756:                 'order' => 'priority ASC',
 757:             )
 758:         ));
 759:         if (isset($_POST['LeadRouting'])) {
 760:             $values = $_POST['Values'];
 761:             $criteria = array();
 762:             for ($i = 0; $i < count($values['field']); $i++) {
 763:                 $tempArr = array(
 764:                     $values['field'][$i], $values['comparison'][$i], $values['value'][$i]);
 765:                 $criteria[] = implode(',', $tempArr);
 766:             }
 767:             $model->criteria = json_encode($criteria);
 768:             $model->attributes = $_POST['LeadRouting'];
 769:             $model->priority = $_POST['LeadRouting']['priority'];
 770:             if (isset($_POST['group'])) {
 771:                 $group = true;
 772:                 $model->groupType = $_POST['groupType'];
 773:             } else {
 774:                 $model->groupType = null;
 775:             }
 776: 
 777:             $model->users = Fields::parseUsers($model->users);
 778:             $check = LeadRouting::model()->findByAttributes(array('priority' => $model->priority));
 779:             if (isset($check)) {
 780:                 $query = "UPDATE x2_lead_routing " .
 781:                         "SET priority=priority+1 " .
 782:                         "WHERE priority>='$model->priority'";
 783:                 $command = Yii::app()->db->createCommand($query);
 784:                 $command->execute();
 785:             }
 786:             if ($model->save()) {
 787:                 $this->redirect('roundRobinRules');
 788:             }
 789:         }
 790: 
 791:         $this->render('customRules', array(
 792:             'model' => $model,
 793:             'users' => $users,
 794:             'dataProvider' => $dataProvider,
 795:             'priorityArray' => $priorityArray,
 796:         ));
 797:     }
 798: 
 799:     /**
 800:      * Delete an existing role.
 801:      *
 802:      * This method is accessed by a form on the {@link AdminController::manageRoles}
 803:      * page to allow for the deletion of admin created roles.  Default system roles
 804:      * (authenticated, guest, and admin) cannot be deleted this way.
 805:      */
 806:     public function actionDeleteRole() {
 807:         $auth = Yii::app()->authManager;
 808:         $roleId = filter_input(INPUT_POST, 'role', FILTER_SANITIZE_STRING);
 809:         if ($roleId) {
 810:             $role = Roles::model()->findByPk($roleId);
 811:             if (!isset($role)) {
 812:                 // Nonexistent role
 813:                 Yii::app()->user->setFlash('error', Yii::t('admin', 'Role does not exist'));
 814:             } elseif (!in_array (
 815:                 strtolower ($role->name), array('authenticated', 'guest', 'admin'))) {
 816: 
 817:                 $auth->removeAuthItem($role->name);
 818:                 $role->delete();
 819:             }
 820:         }
 821:         $this->redirect('manageRoles');
 822:     }
 823: 
 824:     /**
 825:      * Modify the permissions on an existing role.
 826:      *
 827:      * This action is called by a form on the {@link AdminController::actionManageRoles}
 828:      * page to allow for the modification of an existing role.
 829:      */
 830:     public function actionEditRole() {
 831:         $roleInput = FilterUtil::filterArrayInput ($_POST, 'Roles');
 832:         if (!empty($roleInput)) {
 833:             $roleName = isset($roleInput['name']) ? filter_var($roleInput['name'], FILTER_SANITIZE_STRING) : '';
 834:             $timeout = filter_input(INPUT_POST, 'timeout', FILTER_SANITIZE_NUMBER_INT);
 835:             $role = Roles::model()->findByAttributes(array('name' => $roleName));
 836:             if (isset($role)) {
 837:                 $viewPermissions = FilterUtil::filterArrayInput ($_POST, 'viewPermissions');
 838:                 $editPermissions = FilterUtil::filterArrayInput ($_POST, 'editPermissions');
 839:                 $users = FilterUtil::filterArrayInput ($_POST, 'users');
 840:                 $timeout = $timeout * 60;
 841:                 if ($timeout === 0) {
 842:                     $timeout = null;
 843:                 }
 844:                 $role->timeout = $timeout;
 845:                 $role->setUsers($users);
 846: 
 847:                 $role->setViewPermissions($viewPermissions);
 848:                 $role->setEditPermissions($editPermissions);
 849:                 if ($role->save()) {
 850:                     
 851:                 } else {
 852:                     foreach ($model->getErrors() as $err)
 853:                         $errors = $err;
 854:                     $errors = implode(',', $errors);
 855:                     Yii::app()->user->setFlash('error', Yii::t('admin', "Unable to save role: {errors}", array('{errors}' => $errors)));
 856:                 }
 857:             }
 858:         }
 859:         $this->redirect('manageRoles');
 860:     }
 861: 
 862:     /**
 863:      * Create a workflow based exception for a role.
 864:      *
 865:      * This method is called by a form on the {@link AdminController::manageRoles}
 866:      * page to allow for the creation of workflow based exceptions for a role.
 867:      * Workflow exceptions modify which fields are visible or editable based on
 868:      * what stage of a workflow a contact is in.
 869:      */
 870:     public function actionRoleException() {
 871:         $model = new Roles;
 872:         $temp = Workflow::model()->findAll();
 873:         $workflows = array();
 874:         foreach ($temp as $workflow) {
 875:             $workflows[$workflow->id] = $workflow->name;
 876:         }
 877:         $roleInput = filter_input(INPUT_POST,'Roles',FILTER_DEFAULT,FILTER_REQUIRE_ARRAY);
 878:         if (!empty($roleInput)) {
 879:             $workflowId = filter_input(INPUT_POST,'workflow',FILTER_SANITIZE_NUMBER_INT);
 880:             if (!empty($workflowId)){
 881:                 $workflowName = Workflow::model()->findByPk($workflowId)->name;
 882:             } else {
 883:                 $this->redirect('manageRoles');
 884:             }
 885:             $stage = $_POST['workflowStages'];
 886:             if (isset($stage) && !empty($stage))
 887:                 $stageName = X2Model::model('WorkflowStage')->findByAttributes(array('workflowId' => $workflow, 'stageNumber' => $stage))->name;
 888:             else
 889:                 $this->redirect('manageRoles');
 890:             if (!isset($_POST['viewPermissions']))
 891:                 $viewPermissions = array();
 892:             else
 893:                 $viewPermissions = $_POST['viewPermissions'];
 894:             if (!isset($_POST['editPermissions']))
 895:                 $editPermissions = array();
 896:             else
 897:                 $editPermissions = $_POST['editPermissions'];
 898:             $model->attributes = $_POST['Roles'];
 899:             $model->timeout *= 60;
 900:             $oldRole = Roles::model()->findByAttributes(array('name' => $model->name));
 901:             $model->users = "";
 902:             $model->name.=" - $workflowName: $stageName";
 903:             if ($model->save()) {
 904:                 $replacement = new RoleToWorkflow;
 905:                 $replacement->workflowId = $workflow;
 906:                 $replacement->stageId = $stage;
 907:                 $replacement->roleId = $oldRole->id;
 908:                 $replacement->replacementId = $model->id;
 909:                 $replacement->save();
 910:                 $fields = Fields::model()->findAll();
 911:                 $temp = array();
 912:                 foreach ($fields as $field) {
 913:                     $temp[] = $field->id;
 914:                 }
 915: 
 916:                 $both = array_intersect($viewPermissions, $editPermissions);
 917:                 $view = array_diff($viewPermissions, $editPermissions);
 918:                 $neither = array_diff($temp, $viewPermissions);
 919:                 foreach ($both as $field) {
 920:                     $rolePerm = new RoleToPermission;
 921:                     $rolePerm->roleId = $model->id;
 922:                     $rolePerm->fieldId = $field;
 923:                     $rolePerm->permission = 2;
 924:                     $rolePerm->save();
 925:                 }
 926:                 foreach ($view as $field) {
 927:                     $rolePerm = new RoleToPermission;
 928:                     $rolePerm->roleId = $model->id;
 929:                     $rolePerm->fieldId = $field;
 930:                     $rolePerm->permission = 1;
 931:                     $rolePerm->save();
 932:                 }
 933:                 foreach ($neither as $field) {
 934:                     $rolePerm = new RoleToPermission;
 935:                     $rolePerm->roleId = $model->id;
 936:                     $rolePerm->fieldId = $field;
 937:                     $rolePerm->permission = 0;
 938:                     $rolePerm->save();
 939:                 }
 940:             }
 941:             $this->redirect('manageRoles');
 942:         }
 943:     }
 944: 
 945:     /**
 946:      * Modify workflow configuration settings.
 947:      *
 948:      * This method allows for the configuration of workflow backdating functions.
 949:      * These settings control whether or not users are allowed to set workflow
 950:      * completion dates to be in the past, and to what extent they can modify a
 951:      * workflow action once it is marked as complete.
 952:      */
 953:     public function actionWorkflowSettings() {
 954:         $admin = &Yii::app()->settings;
 955:         if (isset($_POST['Admin'])) {
 956: 
 957:             $admin->attributes = $_POST['Admin'];
 958:             // $admin->timeout *= 60;   //convert from minutes to seconds
 959: 
 960: 
 961:             if ($admin->save()) {
 962:                 // $this->redirect('workflowSettings');
 963:             }
 964:         }
 965:         // $admin->timeout = ceil($admin->timeout / 60);
 966:         $this->render('workflowSettings', array(
 967:             'model' => $admin,
 968:         ));
 969:     }
 970: 
 971:     /**
 972:      * A method to echo a dropdown of workflow stages.
 973:      *
 974:      * This method is called via AJAX request and echoes back a dropdown with
 975:      * options consisting of all stages for a particular workflow.
 976:      */
 977:     public function actionGetWorkflowStages() {
 978:         if (isset($_POST['workflow'])) {
 979:             $id = $_POST['workflow'];
 980:             $stages = Workflow::getStages($id);
 981:             foreach ($stages as $key => $value) {
 982:                 echo CHtml::tag('option', array('value' => $key + 1), CHtml::encode($value), true);
 983:             }
 984:         } else {
 985:             echo CHtml::tag('option', array('value' => ''), CHtml::encode(var_dump($_POST)), true);
 986:         }
 987:     }
 988: 
 989:     /**
 990:      * Echo out a series of inputs for a role editor page.
 991:      *
 992:      * This method is called via AJAX from the "Edit Role" portion of the "Manage Roles"
 993:      * page.  Upon selection of a role in the dropdown on that page, this method
 994:      * finds all relevant information about the role and echoes it back as a form
 995:      * to allow for editing of the role.
 996:      */
 997:     public function actionGetRole() {
 998:         $output = "";
 999:         $roleInput = FilterUtil::filterArrayInput ($_POST, 'Roles');
1000:         if (!empty($roleInput)) {
1001:             $roleName = isset($roleInput['name']) ? 
1002:                 filter_var($roleInput['name'], FILTER_SANITIZE_STRING) : '';
1003:             $role = Roles::model()->findByAttributes(array('name' => $roleName));
1004:             if (isset($role)) {
1005:                 $usernames = Yii::app()->db->createCommand()
1006:                     ->select('a.username')
1007:                     ->from('x2_users a')
1008:                     ->join('x2_role_to_user b', 'a.id=b.userId')
1009:                     ->where('b.roleId=:roleId AND b.type="user"', array(':roleId' => $role->id))
1010:                         ->queryColumn();
1011:                 $groupIds = Yii::app()->db->createCommand()
1012:                     ->select('a.id')
1013:                     ->from('x2_groups a')
1014:                     ->join('x2_role_to_user b', 'a.id=b.userId')
1015:                     ->where('b.roleId=:roleId AND b.type="group"', array(':roleId' => $role->id))
1016:                     ->queryColumn();
1017:                 $selected = array_merge($usernames, $groupIds);
1018: 
1019:                 $allUsers = X2Model::getAssignmentOptions(false, true, false);
1020:                 unset($allUsers['admin']);
1021: 
1022:                 $sliderId = 'editTimeoutSlider';
1023:                 $textfieldId = 'editTimeout';
1024:                 if (isset($_GET['mode']) && in_array($_GET['mode'], array('edit', 'exception'))) {
1025:                     // Handle whether this was called from editRole or roleException, they
1026:                     // need different IDs to work on the same page.
1027:                     $sliderId .= "-" . $_GET['mode'];
1028:                     $textfieldId .= "-" . $_GET['mode'];
1029:                 }
1030: 
1031:                 $timeoutSet = $role->timeout !== null;
1032:                 $output.= "
1033:                     <div class='row' id='set-session-timeout-row'>
1034:                     <input id='set-session-timeout' type='checkbox' class='left' " .
1035:                         ($timeoutSet ? 'checked="checked"' : '') . ">
1036:                     <label>" . Yii::t('admin', 'Enable Session Timeout') . "</label>
1037:                     </div>
1038:                 ";
1039:                 $output.= "<div id='timeout-row' class='row' " .
1040:                     ($timeoutSet ? '' : "style='display: none;'") . ">";
1041:                 $output.= Yii::t('admin', 'Set role session expiration time (in minutes).');
1042:                 $output.= "<br />";
1043:                 $output.= $this->widget('zii.widgets.jui.CJuiSlider', array(
1044:                     'value' => $role->timeout / 60,
1045:                     // additional javascript options for the slider plugin
1046:                     'options' => array(
1047:                         'min' => 5,
1048:                         'max' => 1440,
1049:                         'step' => 5,
1050:                         'change' => "js:function(event,ui) {
1051:                                         $('#" . $textfieldId . "').val(ui.value);
1052:                                         $('#save-button').addClass('highlight');
1053:                                     }",
1054:                         'slide' => "js:function(event,ui) {
1055:                                         $('#" . $textfieldId . "').val(ui.value);
1056:                                     }",
1057:                     ),
1058:                     'htmlOptions' => array(
1059:                         'style' => 'width:340px;margin:10px 9px;',
1060:                         'id' => $sliderId
1061:                     ),
1062:                         ), true);
1063:                 $output.= CHtml::activeTextField(
1064:                                 $role, 'timeout', array(
1065:                             'id' => $textfieldId,
1066:                             'disabled' => ($role->timeout !== null ? '' : 'disabled'),
1067:                 ));
1068:                 $output.= "</div>";
1069:                 Yii::app()->clientScript->registerScript('timeoutScript', "
1070:                     $('#set-session-timeout').change (function () {
1071:                         if ($(this).is (':checked')) {
1072:                             $('#timeout-row').slideDown ();
1073:                             $('#" . $textfieldId . "').removeAttr ('disabled');
1074:                         } else {
1075:                             $('#timeout-row').slideUp ();
1076:                             $('#" . $textfieldId . "').attr ('disabled', 'disabled');
1077:                         }
1078:                     });
1079:                     $('#" . $textfieldId . "').val( $('#" . $sliderId . "').slider('value') );
1080:                 ", CClientScript::POS_READY);
1081:                 $output.= "<script>";
1082:                 $output.= Yii::app()->clientScript->echoScripts(true);
1083:                 $output.= "</script>";
1084: 
1085:                 $output.= "<div id='users'><label>Users</label>";
1086:                 $output.= CHtml::dropDownList(
1087:                     'users[]', $selected, $allUsers,
1088:                     array(
1089:                         'class' => 'multiselect',
1090:                         'multiple' => 'multiple',
1091:                         'size' => 8
1092:                     ));
1093:                 $output.= "</div>";
1094:                 $fields = Fields::getFieldsOfModelsWithFieldLevelPermissions ();
1095:                 $fieldIds = array_flip (array_map (function ($field) {
1096:                     return $field->id;
1097:                 }, $fields));
1098:                 $viewSelected = array();
1099:                 $editSelected = array();
1100:                 $fieldUnselected = array();
1101:                 $fieldPerms = RoleToPermission::model()
1102:                     ->findAllByAttributes(array('roleId' => $role->id));
1103:                 foreach ($fieldPerms as $perm) {
1104:                     if (!isset ($fieldIds[$perm->fieldId])) continue;
1105:                     if ($perm->permission == 2) {
1106:                         $viewSelected[] = $perm->fieldId;
1107:                         $editSelected[] = $perm->fieldId;
1108:                     } else if ($perm->permission == 1) {
1109:                         $viewSelected[] = $perm->fieldId;
1110:                     }
1111:                 }
1112:                 foreach ($fields as $field) {
1113:                     $fieldUnselected[$field->id] = X2Model::getModelTitle($field->modelName) . 
1114:                         " - " . $field->attributeLabel;
1115:                 }
1116:                 assert (
1117:                     count ($fieldUnselected) === 
1118:                     count (array_unique (array_keys ($fieldUnselected))));
1119:                 $output.= "<br /><label>View Permissions</label>";
1120:                 $output.= CHtml::dropDownList(
1121:                     'viewPermissions[]', 
1122:                     $viewSelected, 
1123:                     $fieldUnselected, 
1124:                     array(
1125:                         'class' => 'multiselect',
1126:                         'multiple' => 'multiple',
1127:                         'size' => 8,
1128:                         'id' => 'edit-role-field-view-permissions'
1129:                     ));
1130:                 $output.= "<br /><label>Edit Permissions</label>";
1131: 
1132:                 $output.= CHtml::dropDownList(
1133:                     'editPermissions[]', 
1134:                     $editSelected, 
1135:                     $fieldUnselected, 
1136:                     array(
1137:                         'class' => 'multiselect',
1138:                         'multiple' => 'multiple',
1139:                         'size' => 8,
1140:                         'id' => 'edit-role-field-edit-permissions'
1141:                     ));
1142:             }
1143:         }
1144:         echo $output;
1145:     }
1146: 
1147:     /**
1148:      * A catch all page for roles.
1149:      *
1150:      * This action renders a page with forms for the creation, editing, and deletion
1151:      * of roles.  It also displays a grid with all user created roles (default
1152:      * roles are not included and cannot be edited this way).
1153:      */
1154:     public function actionManageRoles() {
1155:         $dataProvider = new CActiveDataProvider('Roles');
1156:         $roles = Yii::app()->db->createCommand()
1157:                 ->select('id, name')
1158:                 ->from('x2_roles')
1159:                 ->queryAll();
1160: 
1161:         $model = new Roles;
1162:         $model->timeout = 60;
1163:         $roleInput = FilterUtil::filterArrayInput ($_POST, 'Roles');
1164:         if (!empty($roleInput)) {
1165:             $model->attributes = $roleInput;
1166:             $model->users = '';
1167:             $viewPermissions = FilterUtil::filterArrayInput ($_POST, 'viewPermissions');
1168:             $editPermissions = FilterUtil::filterArrayInput ($_POST, 'editPermissions');
1169:             $users = FilterUtil::filterArrayInput ($roleInput, 'users');
1170:             $model->timeout *= 60;
1171:             if ($model->timeout === 0) {
1172:                 $model->timeout = null;
1173:             }
1174:             $model->setUsers($users);
1175:             $model->setViewPermissions($viewPermissions);
1176:             $model->setEditPermissions($editPermissions);
1177: 
1178:             if ($model->save()) {
1179:                 
1180:             } else {
1181:                 foreach ($model->getErrors() as $err)
1182:                     $errors = $err;
1183:                 $errors = implode(',', $errors);
1184:                 Yii::app()->user->setFlash('error', Yii::t('admin', "Unable to save role: {errors}", array('{errors}' => $errors)));
1185:             }
1186:             $this->redirect('manageRoles');
1187:         }
1188: 
1189: 
1190:         $this->render('manageRoles', array(
1191:             'dataProvider' => $dataProvider,
1192:             'model' => $model,
1193:             'roles' => $roles,
1194:         ));
1195:     }
1196: 
1197:     /**
1198:      * Render the changelog.
1199:      *
1200:      * This action renders the user changelog page, which contains a list of all
1201:      * changes made by users within the app.
1202:      */
1203:     public function actionViewChangelog() {
1204: 
1205:         $model = new Changelog('search');
1206:         if (isset($_GET['Changelog'])) {
1207:             foreach ($_GET['Changelog'] as $field => $value) {
1208:                 if ($model->hasAttribute($field)) {
1209:                     $model->$field = $value;
1210:                 }
1211:             }
1212:         }
1213:         $this->render('viewChangelog', array(
1214:             'model' => $model,
1215:         ));
1216:     }
1217: 
1218:     /**
1219:      * Export all changelog entries to CSV
1220:      */
1221:     public function actionExportChangelog() {
1222:         ini_set('memory_limit', -1);
1223:         $csv = $this->safePath('changelog.csv');
1224:         $fp = fopen($csv, 'w+');
1225:         $meta = array_keys(Changelog::model()->attributes);
1226:         fputcsv($fp, $meta);
1227:         $records = Changelog::model()->findAll();
1228:         foreach ($records as $record) {
1229:             $line = $record->attributes;
1230:             fputcsv($fp, $line);
1231:         }
1232:         fclose($fp);
1233:     }
1234: 
1235:     /**
1236:      * Delete all changelog entries from the database.
1237:      */
1238:     public function actionClearChangelog() {
1239:         Changelog::model()->deleteAll();
1240:         $this->redirect('viewChangelog');
1241:     }
1242: 
1243:     /**
1244:      * Add notification criteria.
1245:      *
1246:      * This method is called by a form on the "Manage Notification Criteria" page
1247:      * and is used to create a new criteria for generation notifications.
1248:      */
1249:     public function actionAddCriteria() {
1250:         $criteria = new Criteria;
1251:         $users = User::getNames();
1252:         $dataProvider = new CActiveDataProvider('Criteria');
1253:         unset($users['']);
1254:         unset($users['Anyone']);
1255:         $criteria->users = Yii::app()->user->getName();
1256:         if (isset($_POST['Criteria'])) {
1257:             $criteria->attributes = $_POST['Criteria'];
1258:             $str = "";
1259:             $arr = $criteria->users;
1260:             if ($criteria->type == 'assignment' && count($arr) > 1) {
1261:                 $this->redirect('addCriteria');
1262:             }
1263:             if (isset($arr)) {
1264:                 $str = implode(', ', $arr);
1265:             }
1266:             $criteria->users = $str;
1267:             if ($criteria->modelType != null && $criteria->comparisonOperator != null) {
1268:                 if ($criteria->save()) {
1269:                     
1270:                 }
1271:                 $this->refresh();
1272:             }
1273:         }
1274:         $this->render('addCriteria', array(
1275:             'users' => $users,
1276:             'model' => $criteria,
1277:             'dataProvider' => $dataProvider,
1278:         ));
1279:     }
1280: 
1281:     /**
1282:      * Delete a notification criteria.
1283:      *
1284:      * This function is called to delete a user created notification critera.
1285:      * Some criteria are built in to the app and cannot be deleted this way.
1286:      *
1287:      * @param int $id The ID of the criteria to be deleted.
1288:      */
1289:     public function actionDeleteCriteria($id) {
1290: 
1291:         Criteria::model()->deleteByPk($id);
1292:         $this->redirect(array('addCriteria'));
1293:     }
1294: 
1295:     /**
1296:      * Delete a routing rule.
1297:      *
1298:      * This method will delete a custom routing rule that has been configured
1299:      * for the lead distribution process.
1300:      * @param int $id The ID of the rule to be deleted.
1301:      */
1302:     public function actionDeleteRouting($id) {
1303: 
1304:         LeadRouting::model()->deleteByPk($id);
1305:         $this->redirect(array('roundRobinRules'));
1306:     }
1307: 
1308:     /**
1309:      * @deprecated
1310:      * Deprecated function to set user timeout.
1311:      *
1312:      * This method formerly controlled the user session timeout settings for the
1313:      * software.  This setting is now controlled by the "General Settings" page.
1314: 
1315:       public function actionSetTimeout() {
1316: 
1317:       $admin = &Yii::app()->settings; //Admin::model()->findByPk(1);
1318:       if (isset($_POST['Admin'])) {
1319:       $timeout = $_POST['Admin']['timeout'];
1320: 
1321:       $admin->timeout = $timeout;
1322: 
1323:       if ($admin->save()) {
1324:       $this->redirect('index');
1325:       }
1326:       }
1327: 
1328:       $this->render('setTimeout', array(
1329:       'admin' => $admin,
1330:       ));
1331:       } */
1332:     /**
1333:      * @deprecated
1334:      * Deprecated method to set chat polling
1335:      *
1336:      * This method formerly controlled the configuration of chat polling requests.
1337:      * This timeout is now set by the "General Settings" page.
1338: 
1339:       public function actionSetChatPoll() {
1340: 
1341:       $admin = &Yii::app()->settings; //X2Model::model('Admin')->findByPk(1);
1342:       if (isset($_POST['Admin'])) {
1343:       $timeout = $_POST['Admin']['chatPollTime'];
1344: 
1345:       $admin->chatPollTime = $timeout;
1346: 
1347:       if ($admin->save()) {
1348:       $this->redirect('index');
1349:       }
1350:       }
1351: 
1352:       $this->render('setChatPoll', array(
1353:       'admin' => $admin,
1354:       ));
1355:       } */
1356: 
1357:     /**
1358:      * Control general settings for the software.
1359:      *
1360:      * This method renders a page with settings for a variety of admin options.
1361:      * This includes things like Contact name formatting, session timeout and
1362:      * notification poll times, and basic privacy the for action history.
1363:      * These settings are application wide and not per user.
1364:      */
1365:     public function actionAppSettings() {
1366: 
1367:         $admin = &Yii::app()->settings;
1368:         if (isset($_POST['Admin'])) {
1369: 
1370:             // if(!isset($_POST['Admin']['ignoreUpdates']))
1371:             // $admin->ignoreUpdates = 1;
1372:             $oldFormat = $admin->contactNameFormat;
1373:             $admin->attributes = $_POST['Admin'];
1374:             foreach ($_POST['Admin'] as $attribute => $value) {
1375:                 if ($admin->hasAttribute($attribute)) {
1376:                     $admin->$attribute = $value;
1377:                 }
1378:             }
1379:             if (isset($_POST['currency'])) {
1380:                 if ($_POST['currency'] == 'other') {
1381:                     $admin->currency = $_POST['currency2'];
1382:                     if (empty($admin->currency))
1383:                         $admin->addError('currency', Yii::t('admin', 'Please enter a valid currency type.'));
1384:                 } else
1385:                     $admin->currency = $_POST['currency'];
1386:             }
1387:             if ($oldFormat != $admin->contactNameFormat) {
1388:                 if ($admin->contactNameFormat == 'lastName, firstName') {
1389:                     $command = Yii::app()->db->createCommand()->setText('UPDATE x2_contacts SET name=CONCAT(lastName,", ",firstName)')->execute();
1390:                 } elseif ($admin->contactNameFormat == 'firstName lastName') {
1391:                     $command = Yii::app()->db->createCommand()->setText('UPDATE x2_contacts SET name=CONCAT(firstName," ",lastName)')->execute();
1392:                 }
1393:             }
1394:             $admin->timeout *= 60; //convert from minutes to seconds
1395: 
1396: 
1397:             if ($admin->save()) {
1398:                 $this->redirect('appSettings');
1399:             }
1400:         }
1401:         $admin->timeout = ceil($admin->timeout / 60);
1402:         $this->render('appSettings', array(
1403:             'model' => $admin,
1404:         ));
1405:     }
1406: 
1407:     /**
1408:      * Render a page with options for activity feed settings.
1409:      *
1410:      * The administrator is allowed to configure what sort of information should
1411:      * be displayed in the activity feed and for how long. This page sets options
1412:      * for automated deletion of any chosen types after a set time period to help
1413:      * keep the database cleaner.
1414:      */
1415:     public function actionActivitySettings() {
1416: 
1417:         $admin = &Yii::app()->settings;
1418:         $admin->eventDeletionTypes = json_decode($admin->eventDeletionTypes, true);
1419:         if (isset($_POST['Admin'])) {
1420: 
1421:             $admin->eventDeletionTime = $_POST['Admin']['eventDeletionTime'];
1422:             $admin->eventDeletionTypes = json_encode($_POST['Admin']['eventDeletionTypes']);
1423:             if ($admin->save()) {
1424:                 $this->redirect('activitySettings');
1425:             }
1426:         }
1427:         $this->render('activitySettings', array(
1428:             'model' => $admin,
1429:         ));
1430:     }
1431: 
1432:     /**
1433:      * Sets the lead routing type.
1434:      *
1435:      * This method allows for the admin to configure which option to use for lead
1436:      * distribution.  This is what determines the actions of {@link LeadRoutingBehavior}.
1437:      */
1438:     public function actionSetLeadRouting() {
1439: 
1440:         $admin = &Yii::app()->settings; //Admin::model()->findByPk(1);
1441:         if (isset($_POST['Admin'])) {
1442:             $routing = $_POST['Admin']['leadDistribution'];
1443:             $online = $_POST['Admin']['onlineOnly'];
1444:             if ($routing == 'singleUser') {
1445:                 $user = $_POST['Admin']['rrId'];
1446:                 $admin->rrId = $user;
1447:             }
1448: 
1449:             $admin->leadDistribution = $routing;
1450:             $admin->onlineOnly = $online;
1451: 
1452:             if ($admin->save()) {
1453:                 $this->redirect('index');
1454:             }
1455:         }
1456: 
1457:         $this->render('leadRouting', array(
1458:             'admin' => $admin,
1459:         ));
1460:     }
1461: 
1462:     /**
1463:      * Sets the service routing type.
1464:      *
1465:      * This method allows for the admin to configure which option to use for service case
1466:      * distribution.  This is what determines the actions of {@link ServiceRoutingBehavior}.
1467:      */
1468:     public function actionSetServiceRouting() {
1469: 
1470:         $admin = &Yii::app()->settings; //Admin::model()->findByPk(1);
1471:         if (isset($_POST['Admin'])) {
1472:             $routing = $_POST['Admin']['serviceDistribution'];
1473:             $online = $_POST['Admin']['serviceOnlineOnly'];
1474:             if ($routing == 'singleUser') {
1475:                 $user = $_POST['Admin']['srrId'];
1476:                 $admin->srrId = $user;
1477:             } else if ($routing == 'singleGroup') {
1478:                 $group = $_POST['Admin']['sgrrId'];
1479:                 $admin->sgrrId = $group;
1480:             }
1481: 
1482:             $admin->serviceDistribution = $routing;
1483:             $admin->serviceOnlineOnly = $online;
1484: 
1485:             if ($admin->save()) {
1486:                 $this->redirect('index');
1487:             }
1488:         }
1489: 
1490:         $this->render('serviceRouting', array(
1491:             'admin' => $admin,
1492:         ));
1493:     }
1494: 
1495:     /**
1496:      * Configure google integration.
1497:      *
1498:      * This method provides a form for the entry of Google Apps data.  This will
1499:      * allow for users to log in with their Google account and sync X2Engine's calendars
1500:      * with their Google Calendar.
1501:      */
1502:     public function actionGoogleIntegration() {
1503: //
1504: //        $admin = &Yii::app()->settings;
1505: //        if (isset($_POST['Admin'])) {
1506: //            foreach ($admin->attributes as $fieldName => $field) {
1507: //                if (isset($_POST['Admin'][$fieldName])) {
1508: //                    $admin->$fieldName = $_POST['Admin'][$fieldName];
1509: //                }
1510: //            }
1511: //
1512: //            if ($admin->save()) {
1513: //                $this->redirect('googleIntegration');
1514: //            }
1515: //        }
1516: //        $this->render('googleIntegration', array(
1517: //            'model' => $admin,
1518: //        ));
1519: //      return;
1520: 
1521: 
1522:         $credId = Yii::app()->settings->googleCredentialsId;
1523: 
1524:         if ($credId && ($cred = Credentials::model()->findByPk($credId))) {
1525:             $params = array('id' => $credId);
1526:         } else {
1527:             $params = array('class' => 'GoogleProject');
1528:         }
1529:         $url = Yii::app()->createUrl('/profile/createUpdateCredentials', $params);
1530:         $this->redirect($url);
1531:     }
1532: 
1533:     public function actionTwitterIntegration() {
1534:         $credId = Yii::app()->settings->twitterCredentialsId;
1535: 
1536:         if ($credId && ($cred = Credentials::model()->findByPk($credId))) {
1537:             $params = array('id' => $credId);
1538:         } else {
1539:             $params = array('class' => 'TwitterApp');
1540:         }
1541:         $url = Yii::app()->createUrl('/profile/createUpdateCredentials', $params);
1542:         $this->redirect($url);
1543:     }
1544: 
1545:     /**
1546:      * Configure email settings.
1547:      *
1548:      * This allows for configuration of how emails are handled by X2Engine.  The admin
1549:      * can select to use the server that the software is hosted on or a separate mail server.
1550:      */
1551:     public function actionEmailSetup() {
1552: 
1553:         $admin = &Yii::app()->settings;
1554:         Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/manageCredentials.js');
1555:         if (isset($_POST['Admin'])) {
1556:             $admin->attributes = $_POST['Admin'];
1557: 
1558:             if ($admin->save()) {
1559:                 $this->redirect('emailSetup');
1560:             }
1561:         } else {
1562:             // set defaults
1563:             if (!isset($admin->doNotEmailLinkText))
1564:                 $admin->doNotEmailLinkText = Admin::getDoNotEmailLinkDefaultText();
1565:             if (!isset($admin->doNotEmailPage))
1566:                 $admin->doNotEmailPage = Admin::getDoNotEmailDefaultPage();
1567:         }
1568: 
1569:         $this->render('emailSetup', array(
1570:             'model' => $admin,
1571:         ));
1572:     }
1573: 
1574:     /**
1575:      * Form/submit action for adding or customizing a field.
1576:      *
1577:      * This method allows for the creation of custom fields linked to any customizable
1578:      * module in X2Engine.  This is used by "Manage Fields." It is used to reload the
1579:      * form via AJAX.
1580:      * 
1581:      * @param bool $search If set to 1/true, perform a lookup for an existing field
1582:      * @param bool $save If set to 1/true, attempt to save the model; otherwise just echo the form.
1583:      */
1584:     public function actionCreateUpdateField($search = 0, $save = 0, $override = 0) {
1585:         $changedType = false;
1586:         if ($search) {
1587:             // A field is being looked up, to populate form fields for customizing
1588:             // an existing field
1589:             $new = false;
1590:             if (isset($_POST['Fields'])) {
1591:                 $model = Fields::model()->findByAttributes(array_intersect_key($_POST['Fields'], array_fill_keys(array('modelName', 'fieldName'), null)));
1592:             }
1593:         } else {
1594:             // Requesting the form
1595:             $new = true;
1596:         }
1597:         if (!isset($model) || !(bool) $model) {
1598:             // If the field model wasn't found, create the object
1599:             $model = new Fields;
1600:         }
1601: 
1602:         if (isset($_POST['Fields']) && ($model->isNewRecord || $override)) {
1603:             $oldType = $model->type;
1604:             $model->attributes = $_POST['Fields'];
1605:             // field name exists if model refers to actual db record
1606:             if ($model->fieldName && $model->type !== $oldType)
1607:                 $changedType = true;
1608:         }
1609: 
1610:         $message = '';
1611:         $error = false;
1612: 
1613:         if (isset($_POST['Fields']) && $save) {
1614:             $model->attributes = $_POST['Fields'];
1615:             if (!isset($_POST['Fields']['linkType'])) {
1616:                 // This can be removed if we ever make the linkType attribute more structured
1617:                 // (i.e. field type-specific link type validation rules)
1618:                 $model->linkType = null;
1619:             }
1620: 
1621:             // Set the default value
1622:             if (isset($_POST['AmorphousModel'])) {
1623:                 $aModel = $_POST['AmorphousModel'];
1624:                 $model->defaultValue = $model->parseValue($aModel['customized_field']);
1625:             }
1626: 
1627:             $new = $model->isNewRecord;
1628:             $model->modified = 1; // The field has been modified
1629:             if ($new) // The field should be marked as custom since the user is adding it
1630:                 $model->custom = 1;
1631: 
1632:             if ($model->save()) {
1633:                 // Clear cache to reload ActiveRecord schema
1634:                 $cache = Yii::app()->cache;
1635:                 if (isset($cache))
1636:                     $cache->flush();
1637: 
1638:                 $message = $new ? Yii::t('admin', 'Field added.') : Yii::t('admin', 'Field modified successfully.');
1639:                 if ($new) {
1640:                     $model = new Fields;
1641:                 }
1642:             } else {
1643:                 $error = true;
1644:                 $message = Yii::t('admin', 'Please correct the following errors.');
1645:             }
1646:         }
1647:         $dummyModel = new AmorphousModel;
1648:         $dummyModel->addField($model, 'customized_field');
1649:         $dummyModel->setAttribute('customized_field', $model->defaultValue);
1650: 
1651:         $this->renderPartial('createUpdateField', array(
1652:             'model' => $model,
1653:             'new' => $new,
1654:             'dummyModel' => $dummyModel,
1655:             'message' => $message,
1656:             'error' => $error,
1657:             'changedType' => $changedType,
1658:         ));
1659:     }
1660: 
1661:     /**
1662:      * Delete a field.
1663:      *
1664:      * This method allows for the deletion of custom fields.  Default fields cannot
1665:      * be deleted in this way.
1666:      */
1667:     public function actionRemoveField($getCount = false) {
1668: 
1669:         if (isset($_POST['field']) && $_POST['field'] != "") {
1670:             $id = $_POST['field'];
1671:             $field = Fields::model()->findByPk($id);
1672:             if ($getCount) {
1673:                 $nonNull = $field->countNonNull();
1674:                 if ($nonNull) {
1675:                     echo Yii::t('admin', 'This field contains data; it is non-empty in {n} records.', array(
1676:                         '{n}' => '<span style="color:red;font-weight:bold">' . $nonNull . '</span>'
1677:                             )
1678:                     );
1679:                 } else {
1680:                     echo Yii::t('admin', 'The field appears to be empty. Deleting it will not result in any data loss.');
1681:                 }
1682:                 Yii::app()->end();
1683:             }
1684:             $field->delete();
1685:         }
1686:         $this->redirect('manageFields');
1687:     }
1688: 
1689:     /**
1690:      * General field management.
1691:      *
1692:      * This action serves as the landing page for all of the custom field related
1693:      * actions within the software.
1694:      */
1695:     public function actionManageFields() {
1696:         // New model for the form:
1697:         $model = new Fields;
1698: 
1699:         // Set up grid view:
1700:         $searchModel = new Fields('search');
1701:         $criteria = new CDbCriteria;
1702:         $criteria->addCondition('modified=1');
1703:         $searchModel->setAttributes(
1704:                 isset($_GET['Fields']) ? $_GET['Fields'] : array(), false);
1705:         foreach ($searchModel->attributes as $name => $value) {
1706:             $criteria->compare($name, $value);
1707:         }
1708:         $pageSize = Profile::getResultsPerPage();
1709:         $dataProvider = new SmartActiveDataProvider('Fields', array(
1710:             'criteria' => $criteria,
1711:             'pagination' => array(
1712:                 'pageSize' => Profile::getResultsPerPage(),
1713:             ),
1714:         ));
1715: 
1716:         // Set up fields list
1717:         $fields = Fields::model()->findAllByAttributes(array('custom' => '1'));
1718:         $arr = array();
1719:         foreach ($fields as $field) {
1720:             $arr[$field->id] = $field->attributeLabel;
1721:         }
1722: 
1723:         $this->render('manageFields', array(
1724:             'dataProvider' => $dataProvider,
1725:             'model' => $model,
1726:             'searchModel' => $searchModel,
1727:             'fields' => $arr,
1728:         ));
1729:     }
1730: 
1731:     public function actionDeleteMenuItem () {
1732:         if (!isset ($_POST['id'])) $this->badRequest ();
1733:         $model = Modules::model ()->findByPk ($_POST['id']);
1734:         if (!$model || !in_array ($model->moduleType, array ('link', 'recordLink'))) {
1735:             $this->badRequest ();
1736:         }
1737:         if ($model->delete ()) {
1738:             echo 'success';
1739:         }
1740:     }
1741: 
1742:     public function actionEditMobileForms () {
1743:         Yii::import ('application.modules.mobile.models.*');
1744:         Yii::import ('application.modules.mobile.*');
1745:         Yii::import ('application.models.formModels.*');
1746:         $model = new EditMobileFormsFormModel;
1747:         if (isset ($_POST['EditMobileFormsFormModel'])) {
1748:             $model->setAttributes ($_POST['EditMobileFormsFormModel']);
1749:             if ($model->validate ()) {
1750:                 Yii::app()->db->createCommand ()
1751:                     ->delete (
1752:                         'x2_mobile_layouts',
1753:                         'modelName=:modelName',
1754:                         array (':modelName' => $model->modelName));
1755:                 foreach (array ('defaultView', 'defaultForm') as $layoutName) {
1756:                     $layout = new MobileLayouts;
1757:                     $layout->modelName = $model->modelName;
1758:                     $layout->layout = $model->$layoutName;
1759:                     $layout->defaultView = $layoutName === 'defaultView';
1760:                     $layout->defaultForm = $layoutName === 'defaultForm';
1761:                     $layout->save ();
1762:                 }
1763:                 Yii::app()->user->setFlash (
1764:                     'success', Yii::t('app', 'Layout updated'));
1765:             }
1766:         }
1767: 
1768:         $modules = MobileModule::supportedModules (new CDbCriteria (array (
1769:             'condition' => 'editable'
1770:         )));
1771: 
1772:         $modelList = array('' => '---');
1773:         foreach ($modules as $module) {
1774:             if ($module->name == 'marketing')
1775:                 $modelList['Campaign'] = Yii::t('marketing', 'Campaign');
1776:             elseif ($module->name == 'opportunities')
1777:                 $modelList['Opportunity'] = Yii::t('opportunities', 'Opportunity');
1778:             elseif ($module->name == 'products')
1779:                 $modelList['Product'] = Yii::t('products', 'Product');
1780:             elseif ($module->name == 'quotes')
1781:                 $modelList['Quote'] = Yii::t('quotes', 'Quote');
1782:             else
1783:                 $modelList[ucfirst($module->name)] = Yii::t('app', $module->title);
1784:         }
1785: 
1786:         $this->render('editMobileForms', array(
1787:             'model' => $model,
1788:             'recordTypes' => $modelList,
1789:         ));
1790:     }
1791: 
1792:     public function actionGetMobileLayouts ($modelName) {
1793:         Yii::import ('application.modules.mobile.models.*');
1794: 
1795:         // find or generate layouts
1796:         $formLayout = MobileLayouts::model ()->findByAttributes (array (
1797:             'modelName' => $modelName,
1798:             'defaultView' => 1
1799:         ));
1800:         $viewLayout = MobileLayouts::model ()->findByAttributes (array (
1801:             'modelName' => $modelName,
1802:             'defaultForm' => 1
1803:         ));
1804:         $formLayout = $formLayout ? $formLayout->layout : null;
1805:         $viewLayout = $viewLayout ? $viewLayout->layout : null;
1806:         if (!$formLayout)
1807:             $formLayout = MobileLayouts::generateDefaultLayout ('form', $modelName);
1808:         if (!$viewLayout)
1809:             $viewLayout = MobileLayouts::generateDefaultLayout ('view', $modelName);
1810: 
1811:         list ($formLayout, $unselectedForm) = MobileLayouts::getFieldOptions (
1812:             $formLayout, $modelName);
1813:         list ($viewLayout, $unselectedView) = MobileLayouts::getFieldOptions (
1814:             $viewLayout, $modelName);
1815: 
1816:         echo CJSON::encode (array (
1817:             'defaultForm' => $formLayout, 
1818:             'defaultView' => $viewLayout, 
1819:             'defaultFormUnselected' => $unselectedForm, 
1820:             'defaultViewUnselected' => $unselectedView, 
1821:         ));
1822:     }
1823: 
1824:     /**
1825:      * Create a static page.
1826:      *
1827:      * This method allows the admin to create a static page to go on the top bar
1828:      * menu.  The page is a basic doc editor which is then saved as a Module record
1829:      * of type "Document."
1830:      */
1831:     public function actionCreatePage() {
1832:         $model = Yii::createComponent (array (
1833:             'class' => 'application.models.formModels.CreatePageFormModel',
1834:         ));
1835:         if (isset ($_POST[get_class ($model)])) {
1836:             $model->setAttributes ($_POST[get_class ($model)]);
1837:             if ($model->validate ()) {
1838:                 $module = new Modules;
1839:                 $module->adminOnly = 0;
1840:                 $module->toggleable = 1;
1841:                 $module->custom = 1;
1842:                 $module->visible = 1;
1843:                 $module->editable = 0;
1844:                 $module->searchable = 0;
1845:                 $module->menuPosition = Modules::model()->count();
1846:                 $module->linkOpenInNewTab = $model->openInNewTab;
1847:                 $type = $model->getSelection () === 'topLinkUrl' ? 'link' : 'recordLink';
1848:                 $module->moduleType = $type;
1849:                 if ($type === 'link') {
1850:                     $module->title = $model->topLinkText;
1851:                     $module->linkHref = $model->topLinkUrl;
1852:                 } else {
1853:                     $module->linkRecordType = $model->recordType;
1854:                     $module->linkRecordId = $model->recordId;
1855:                 }
1856:                 if ($module->save()) {
1857:                     Yii::app()->user->setFlash (
1858:                         'success', Yii::t('app', 'Created top bar link.'));
1859:                     $this->redirect(array('/admin/createPage'));
1860:                 } else {
1861:                     Yii::app()->user->setFlash (
1862:                         'error', Yii::t('app', 'Failed to create top bar link.'));
1863:                 }
1864:             }
1865:         }
1866: 
1867:         $this->render('createPage', array(
1868:             'model' => $model,
1869:         ));
1870:     }
1871: 
1872:     /**
1873:      * @deprecated
1874:      * View a page that has been created.
1875:      *
1876:      * This method is what is called when a user clicks the top bar link to a static
1877:      * page that has been previously created.  Nearly identical to a document view
1878:      * but without the widgets in the layout. This function is no longer used in
1879:      * favor of the document view action.
1880:      *
1881:      * @param int $id The ID of the page being viewed.
1882:      */
1883:     public function actionViewPage($id) {
1884:         $model = CActiveRecord::model('Docs')->findByPk($id);
1885:         if (!isset($model))
1886:             $this->redirect(array('/docs/docs/index'));
1887: 
1888:         $this->render('viewTemplate', array(
1889:             'model' => $model,
1890:         ));
1891:     }
1892: 
1893:     public function actionEditGlobalCss () {
1894:         $formModel = new GlobalCSSFormModel;
1895:         if (isset ($_POST['GlobalCSSFormModel'])) {
1896:             $formModel->setAttributes ($_POST['GlobalCSSFormModel']);
1897:             if ($formModel->save ()) {
1898:                 $formModel->css = GlobalCSSFormModel::getGlobalCss ();
1899:                 X2Flashes::addFlash (
1900:                     'success', 
1901:                     Yii::t('app', 'CSS saved'));
1902:             }
1903:         } else {
1904:             $formModel->css = GlobalCSSFormModel::getGlobalCss ();
1905:             if (!$formModel->css) {
1906:                 X2Flashes::addFlash (
1907:                     'error', 
1908:                     Yii::t('app', 'Could not read file '.GlobalCSSFormModel::getGlobalCssPath ()));
1909:             }
1910:         }
1911:         $this->render('editGlobalCss', array(
1912:             'formModel' => $formModel,
1913:         ));
1914:     }
1915: 
1916:     /**
1917:      * Change the title of a module.
1918:      *
1919:      * This allows for the configuration of the display name of a module. Before
1920:      * version 5.0, this would not affect text other than the top bar menu.
1921:      */
1922:     public function actionRenameModules() {
1923:         $order = Modules::model()->findAllByAttributes(array(
1924:             'visible' => 1,
1925:         ));
1926:         $menuItems = array();
1927:         $itemNames = array();
1928:         foreach ($order as $module) {
1929:             $menuItems[$module->name] = Yii::t('app', $module->title);
1930:             if ($module->moduleType === 'module' && ($module->custom || $module->name === 'bugReports'))
1931:                 $itemNames[$module->name] = Modules::itemDisplayName($module->name);
1932:         }
1933:         foreach ($menuItems as $key => $value) {
1934:             $menuItems[$key] = preg_replace('/&#58;/', ':', $value); // decode any colons
1935:         }
1936: 
1937:         if (isset($_POST['module']) && isset($_POST['name'])) {
1938:             $module = $_POST['module'];
1939:             $name = $_POST['name'];
1940:             if (empty($module)) {
1941:                 Yii::app()->user->setFlash('error', Yii::t('admin', "You must select a module."));
1942:             } else {
1943:                 $moduleRecord = Modules::model()->findByAttributes(
1944:                         array(
1945:                             'name' => $module,
1946:                             'title' => $menuItems[$module],
1947:                         )
1948:                 );
1949:                 if(isset($moduleRecord)){
1950:                     $match = Modules::model()->findByAttributes(array('title'=>$name));
1951:                     $itemName = isset($_POST['itemName'])? $_POST['itemName'] : "";
1952: 
1953:                     if (empty($name)) {
1954:                         Yii::app()->user->setFlash('error', Yii::t('admin', "You must specify a title."));
1955:                     } else if (isset($match) && ($match->name !== $moduleRecord->name)) {
1956:                         Yii::app()->user->setFlash('error', Yii::t('admin', "A module with this title already exists."));
1957:                     } else {
1958:                         $moduleRecord->title = $name;
1959:                         if (!empty($itemName) && ($moduleRecord->custom || $moduleRecord->name === 'bugReports'))
1960:                             $moduleRecord->itemName = $itemName;
1961: 
1962:                         if ($moduleRecord->retitle ($name)) {
1963:                             $this->redirect('index');
1964:                         }
1965:                     }
1966:                 }
1967:             }
1968:         }
1969: 
1970:         $this->render('renameModules', array(
1971:             'modules' => $menuItems,
1972:             'itemNames' => $itemNames,
1973:         ));
1974:     }
1975: 
1976:     
1977: 
1978:     /**
1979:      * Re-arrange the top bar menu.
1980:      *
1981:      * This form allows for the admin to change the order and visibility of top bar
1982:      * menu items for all users.
1983:      */
1984:     public function actionManageModules() {
1985: 
1986:         $modules = Modules::model()->findAll();
1987:         usort ($modules, function ($a, $b) {
1988:             $aPos = $a->menuPosition === null ? INF : ((int) $a->menuPosition);
1989:             $bPos = $b->menuPosition === null ? INF : ((int) $b->menuPosition);
1990:             if ($aPos < $bPos) {
1991:                 return -1;
1992:             } elseif ($aPos > $bPos) {
1993:                 return 1;
1994:             } else {
1995:                 return 0;
1996:             }
1997:         });
1998: 
1999:         $selectedItems = array ();
2000:         $deletableOptions = array ();
2001:         $menuItems = array ();
2002: 
2003:         foreach ($modules as $module) {
2004:             if ($module->name != 'users') {
2005:                 if (in_array ($module->moduleType, array ('link', 'recordLink'))) {
2006:                     $deletableOptions[] = $module->id;
2007:                 }
2008:                 $menuItems[$module->id] = $module->getTitle ();
2009:                 if ($module->visible) {
2010:                     $selectedItems[] = $module->id;
2011:                 }
2012:             }
2013:         }
2014: 
2015: 
2016:         if (isset($_POST['formSubmit'])) {
2017:             $selectedItems = isset($_POST['menuItems']) ? $_POST['menuItems'] : array();
2018:             $hiddenModuleIds = array_keys (
2019:                 array_diff_key ($menuItems, array_flip ($selectedItems)));
2020: 
2021:             // validate module ids
2022:             $qpg = new QueryParamGenerator;
2023:             $count = (int) Yii::app()->db->createCommand ("
2024:                 select count(*) from x2_modules
2025:                 where id in ".$qpg->bindArray (
2026:                     array_merge ($selectedItems, $hiddenModuleIds), true))
2027:                 ->queryScalar ($qpg->getParams ());
2028: 
2029:             if ($count !== count (array_merge ($selectedItems, $hiddenModuleIds))) {
2030:                 Yii::app()->user->setFlash (
2031:                     'error', Yii::t('app', 'Selected module(s) not found.'));
2032:             } elseif (Modules::updateTopBarLinks ($selectedItems, $hiddenModuleIds)) {
2033:                 Yii::app()->user->setFlash (
2034:                     'success', Yii::t('app', 'Updated top bar links.'));
2035:                 $this->redirect ('manageModules');
2036:             } else {
2037:                 Yii::app()->user->setFlash (
2038:                     'error', Yii::t('app', 'Failed to update top bar links.'));
2039:             }
2040:         }
2041: 
2042:         $this->render('manageModules', array(
2043:             'selectedItems' => $selectedItems,
2044:             'menuItems' => $menuItems,
2045:             'deletableOptions' => $deletableOptions
2046:         ));
2047:     }
2048: 
2049:     /**
2050:      * Upload a custom logo
2051:      *
2052:      * This method allows for the admin to upload their own logo to go in place of
2053:      * the X2Engine logo in the top left corner of the software.
2054:      */
2055:     public function actionUploadLogo() {
2056:         Yii::import ('application.models.formModels.UploadLogoFormModel');
2057:         $formModel = new UploadLogoFormModel;
2058: 
2059:         if (isset ($_POST['UploadLogoFormModel']) && 
2060:             (isset ($_FILES['UploadLogoFormModel']))) {
2061: 
2062:             $adminProf = Yii::app()->params->adminProfile;
2063:             $formModel->setAttributes($_POST['UploadLogoFormModel']);
2064:             $formModel->menuLogoUpload = CUploadedFile::getInstance($formModel, 'menuLogoUpload');
2065:              
2066:             $uploaded = false;
2067:             if ($formModel->validate ()) {
2068:                 foreach (array (
2069:                     'menuLogoUpload') as $upload) {
2070: 
2071:                     if ($formModel->$upload) {
2072:                         $fileName = 'uploads/protected/logos/' . $formModel->$upload->getName ();
2073:                         if ($formModel->$upload->saveAs ($fileName)) {
2074:                             $uploaded = true;
2075:                             if ($upload === 'menuLogoUpload') {
2076:                                 $associationType = 'logo';
2077:                             } else {
2078:                                 $associationType = 'loginLogo';
2079:                             }
2080:                             $oldLogo = Media::model()->findByAttributes(
2081:                                 array(
2082:                                     'associationId' => $adminProf->id,
2083:                                     'associationType' => $associationType
2084:                                 ));
2085:                             $logo = new Media;
2086:                             $logo->associationType = $associationType;
2087:                             $logo->associationId = $adminProf->id;
2088:                             $logo->name = $fileName;
2089:                             $logo->fileName = $fileName;
2090: 
2091:                             if ($logo->save () && $oldLogo) {
2092:                                 $oldLogo->delete ();
2093:                             }
2094:                         } else {
2095:                             $formModel->addError(
2096:                                 $upload, Yii::t('admin', 'File could not be uploaded'));
2097:                         }
2098:                     }
2099:                 }
2100:             }
2101:             if (!$formModel->hasErrors () && $uploaded) {
2102:                 Yii::app()->user->setFlash(
2103:                     'success', Yii::t('admin', 'Logo uploaded.'));
2104:                 $this->redirect ('uploadLogo');
2105:             }
2106:         }
2107: 
2108:         $this->render('uploadLogo', array (
2109:             'formModel' => $formModel
2110:         ));
2111:     }
2112: 
2113:     /**
2114:      * Reverts the logo back to X2Engine.
2115:      */
2116:     public function actionToggleDefaultLogo($logoType) {
2117:         if (!in_array ($logoType, array ('logo'))) {
2118:             throw new CHttpException (400, Yii::t('admin', 'Bad request'));
2119:         }
2120: 
2121:         $adminProf = Yii::app()->params->adminProfile;
2122:         $logo = Media::model()->findByAttributes(array(
2123:             'associationId' => $adminProf->id, 'associationType' => $logoType));
2124:         if ($logo) {
2125:             $logo->delete(); 
2126:             Yii::app()->user->setFlash(
2127:                 'success', Yii::t('admin', 'Logo restored.'));
2128:         } else {
2129:             Yii::app()->user->setFlash(
2130:                 'error', Yii::t('admin', 'Failed to restore logo.'));
2131:         }
2132:         $this->redirect(array('uploadLogo'));
2133:     }
2134: 
2135:     /**
2136:      * Create or edit translations.
2137:      *
2138:      * This method allows the admin to access the X2Engine built in translation manager.
2139:      * Any translation for any language can be edited and saved from here, and new
2140:      * ones can be added.
2141:      */
2142:     public function actionTranslationManager() {
2143:         $this->layout = null;
2144:         $messagePath = 'protected/messages';
2145:         include('protected/components/TranslationManager.php');
2146:         // die('hello:'.var_dump($_POST));
2147:     }
2148: 
2149:     /**
2150:      * Function to convert custom modules to be in line with the current codebase.
2151:      *
2152:      * This function takes any pre-3.5.1 custom module and performs all necessary
2153:      * operations to make the module compatible with the latest version. Additionally
2154:      * an optional "updateFlag" parameter can be passed, in which case the custom
2155:      * module will have its file contents re-generated to be at the latest version
2156:      * of the template files.
2157:      * TODO: clean up backupFlag code. backupFlag checks no longer necessary since conversion now
2158:      * aborts when backup fails.
2159:      */
2160:     public function actionConvertCustomModules() {
2161:         $status = array();
2162:         if (!empty($_POST)) {
2163:             $updateFlag = false;
2164:             if (isset($_POST['updateFlag']) && $_POST['updateFlag'] == "Yes") {
2165:                 $updateFlag = true; // We need to update file contents as well.
2166:             }
2167:             $modules = X2Model::model('Modules')->findAllByAttributes(array('custom' => 1));
2168:             if (count($modules) == 0) { // There are no custom modules...
2169:                 $status['admin']['error'] = Yii::t('admin', 'Fatal error - No custom modules found.');
2170:                 $status['admin']['title'] = Yii::t('admin', 'Module Conversion');
2171:             }
2172:             foreach ($modules as $module) {
2173:                 $moduleName = $module->name;
2174:                 if (empty ($moduleName)) {
2175:                     $status['admin']['messages'][] = Yii::t('admin', 'Warning: custom module with id :id exists without a "name." Skipping...', array(
2176:                         ':id' => $module->id,
2177:                     ));
2178:                     $status['admin']['title'] = Yii::t('admin', 'Module Conversion');
2179:                     continue;
2180:                 }
2181:                 $modulePath = 'protected/modules/' . $moduleName;
2182:                 $ucName = ucfirst($moduleName);
2183:                 if (is_dir($modulePath)) {
2184:                     $failed = false;
2185:                     // Log everything in the "status" array
2186:                     $status[$moduleName] = array(
2187:                         'title' => $module->title,
2188:                         'messages' => array(),
2189:                         'error' => null
2190:                     );
2191:                     $status[$moduleName]['messages'][] = Yii::t('admin', "Module exists") .
2192:                             ": $moduleName";
2193:                     // Attempt to make a backup
2194:                     if (FileUtil::ccopy($modulePath, 'backup/modules/' . $moduleName)) {
2195:                         $backupFlag = true;
2196:                         $status[$moduleName]['messages'][] = Yii::t('admin', 'Module successfully ' .
2197:                                         'backed up in backup/modules/{moduleName}', array(
2198:                                     '{moduleName}' => $moduleName
2199:                         ));
2200:                     } else {
2201:                         $backupFlag = false;
2202:                         $status[$moduleName]['error'] = Yii::t('admin', 'Backup failed. Unable to write to backup directory. ' .
2203:                                         'Aborting module conversion.');
2204:                         $this->render('convertCustomModules', array(
2205:                             'status' => $status,
2206:                         ));
2207:                         Yii::app()->end();
2208:                     }
2209:                     if (file_exists($modulePath . '/controllers/DefaultController.php')) {
2210:                         // Controller needs to be updated to the new format
2211:                         $renamed = rename($modulePath . '/controllers/DefaultController.php', $modulePath . '/controllers/' . $ucName . 'Controller.php'
2212:                         );
2213:                         if ($renamed) {
2214:                             $status[$moduleName]['messages'][] = Yii::t('admin', '{default} still existed and was successfully renamed to ' .
2215:                                             '{controller}.', array(
2216:                                         '{default}' => 'DefaultController',
2217:                                         '{controller}' => $ucName . 'Controller',
2218:                             ));
2219:                             $file = Yii::app()->file->set($modulePath . '/controllers/' .
2220:                                     $ucName . 'Controller.php');
2221:                             $contents = $file->getContents();
2222:                             $contents = str_replace(array('DefaultController'), array($ucName . 'Controller'), $contents);
2223:                             $success = $file->setContents($contents);
2224: 
2225:                             if ($success !== false) {
2226:                                 $status[$moduleName]['messages'][] = Yii::t('admin', 'Class declaration successfully altered.');
2227:                             } else {
2228:                                 $status[$moduleName]['error'] = Yii::t('admin', 'Fatal error - Unable to change class declaration. ' .
2229:                                                 'Aborting module conversion.');
2230:                                 $failed = true;
2231:                                 if ($backupFlag) {
2232:                                     FileUtil::rrmdir($modulePath);
2233:                                     if (FileUtil::ccopy('backup/modules/' . $moduleName, $modulePath)) {
2234:                                         $status[$moduleName]['error'] .= " " . Yii::t('admin', 'Module backup was successfully restored.');
2235:                                     }
2236:                                 }
2237:                             }
2238:                         } else { // Fail for this module, restore from backup if we were able to.
2239:                             $status[$moduleName]['error'] = Yii::t('admin', 'Fatal error - Unable to rename controller class. ' .
2240:                                             'Aborting module conversion.');
2241:                             $failed = true;
2242:                             if ($backupFlag) {
2243:                                 FileUtil::rrmdir($modulePath);
2244:                                 if (FileUtil::ccopy('backup/modules/' . $moduleName, $modulePath)) {
2245:                                     $status[$moduleName]['error'].=" " . Yii::t('admin', 'Module backup was successfully restored.');
2246:                                 }
2247:                             }
2248:                         }
2249:                     }
2250:                     if (is_dir($modulePath . '/views/default' && !$failed)) {
2251:                         // The view files need to be updated to the new format
2252:                         if (is_dir($modulePath . '/views/' . $moduleName)) {
2253:                             FileUtil::ccopy($modulePath . '/views/default/', $modulePath . '/views/' . $moduleName);
2254:                             $status[$moduleName]['messages'][] = Yii::t('admin', 'Module view folder already exists. View files successfully copied.');
2255:                         } else {
2256:                             $renamed = rename($modulePath . '/views/default', $modulePath . '/views/' . $moduleName);
2257:                             if ($renamed) {
2258:                                 $status[$moduleName]['messages'][] = Yii::t('admin', 'Module view folder successfully renamed.');
2259:                             } else {
2260:                                 $status[$moduleName]['error'] = Yii::t('admin', 'Fatal error - Unable to rename module view folder. ' .
2261:                                                 'Aborting module conversion.');
2262:                                 $failed = true;
2263:                                 if ($backupFlag) {
2264:                                     FileUtil::rrmdir($modulePath);
2265:                                     if (FileUtil::ccopy('backup/modules/' . $moduleName, $modulePath)) {
2266:                                         $status[$moduleName]['error'] .= " " . Yii::t('admin', 'Module backup was successfully restored.');
2267:                                     }
2268:                                 }
2269:                             }
2270:                         }
2271:                     }
2272:                     $viewDir = $modulePath . '/views/' . $moduleName;
2273:                     if (is_dir($viewDir) && !$failed) {
2274:                         // Update view files to use item name from database
2275:                         $viewFiles = scandir($viewDir);
2276:                         $success = true;
2277:                         foreach ($viewFiles as $filename) {
2278:                             if (!preg_match('/^\w+\.php$/', $filename))
2279:                                 continue;
2280:                             $file = Yii::app()->file->set($viewDir . "/" . $filename);
2281:                             $contents = $file->getContents();
2282:                             $contents = str_replace('$moduleConfig[\'recordName\']', 'Modules::itemDisplayName()', $contents);
2283:                             if ($filename === 'index.php') {
2284:                                 // Replace the gridview title
2285:                                 $searchPattern = '\'title\'=>$moduleConfig[\'title\']';
2286:                                 $replacement = '\'title\'=>Modules::displayName(true, ' .
2287:                                         '$moduleConfig[\'moduleName\'])';
2288:                                 $contents = str_replace($searchPattern, $replacement, $contents);
2289:                             }
2290:                             $success = $success && $file->setContents($contents);
2291:                         }
2292:                         if (!$success) {
2293:                             $status[$moduleName]['error'] = Yii::t('admin', 'Fatal error - Unable to update view files. ' .
2294:                                             'Aborting module conversion.');
2295:                             $failed = true;
2296:                             if ($backupFlag) {
2297:                                 FileUtil::rrmdir($modulePath);
2298:                                 if (FileUtil::ccopy('backup/modules/' . $moduleName, $modulePath)) {
2299:                                     $status[$moduleName]['error'] .= " " . Yii::t('admin', 'Module backup was successfully restored.');
2300:                                 }
2301:                             }
2302:                         }
2303:                     }
2304: 
2305:                     $auth = Yii::app()->authManager;
2306:                     // Check for a common access item's existence
2307:                     $testItem = $auth->getAuthItem($ucName . 'ReadOnlyAccess');
2308:                     // It doesn't exist, we need to create permissions for this module.
2309:                     if (is_null($testItem)) {
2310:                         $this->createDefaultModulePermissions($ucName);
2311:                         $status[$moduleName]['messages'][] = Yii::t('admin', 'Permissions configuration complete.');
2312:                     }
2313:                     if ($updateFlag) {
2314:                         // If they specified we need to update, re-generate the custom module
2315:                         // from the template files.
2316:                         include('protected/modules/' . $moduleName . '/' . $moduleName . 'Config.php');
2317:                         $this->createSkeletonDirectories($moduleName, $module->title);
2318:                         $this->writeConfig($moduleConfig['title'], $moduleConfig['moduleName'], $moduleConfig['recordName']);
2319:                         $status[$moduleName]['messages'][] = Yii::t('admin', 'Module files updated to the latest version.');
2320:                     }
2321:                 }
2322:             }
2323:             $authCache = Yii::app()->authCache;
2324:             if (isset($authCache)) // Auth cache needs to be cleared to reset cached permissions
2325:                 $authCache->clear();
2326:         }
2327:         $this->render('convertCustomModules', array(
2328:             'status' => $status,
2329:         ));
2330:     }
2331: 
2332:     /**
2333:      * Creates a new custom module.
2334:      *
2335:      * This method allows for the creation of admin defined modules to use in the
2336:      * software. These modules are more basic in functionality than most other X2
2337:      * modules, but are fully customizable from the studio.
2338:      */
2339:     public function actionCreateModule() {
2340: 
2341:         $errors = array();
2342: 
2343:         if (isset($_POST['moduleName'])) {
2344: 
2345:             $title = trim($_POST['title']);
2346:             $recordName = trim($_POST['recordName']);
2347: 
2348:             $moduleName = trim($_POST['moduleName']);
2349: 
2350:             // are there any non-alphanumeric or _ chars? or non-alpha characters at the beginning?
2351:             if (preg_match('/\W/', $moduleName) || preg_match('/^[^a-zA-Z]+/', $moduleName)) {
2352:                 $errors[] = Yii::t('module', 'Invalid table name'); //$this->redirect('createModule');
2353:             }
2354: 
2355:             if ($moduleName == '') // we will attempt to use the title as the backend name, if possible
2356:                 $moduleName = $title;
2357: 
2358:             if ($recordName == '') // use title for record name if none is provided
2359:                 $recordName = $title;
2360: 
2361:             $trans = include('protected/data/transliteration.php');
2362: 
2363:             // replace characters with their A-Z equivalent, if possible
2364:             $moduleName = strtolower(strtr($moduleName, $trans));
2365: 
2366:             // now remove all remaining non-alphanumeric or _ chars
2367:             $moduleName = preg_replace('/\W/', '', $moduleName);
2368: 
2369:             // remove any numbers or _ from the beginning
2370:             $moduleName = preg_replace('/^[0-9_]+/', '', $moduleName);
2371: 
2372: 
2373:             if ($moduleName == '') { // if there is nothing left of moduleName at this point,
2374:                 $moduleName = 'module' . substr(time(), 5);  // just generate a random one
2375:             }
2376: 
2377:             if (!is_null(Modules::model()->findByAttributes(array('title' => $title))) ||
2378:                     !is_null(Modules::model()->findByAttributes(array('name' => $moduleName)))) {
2379:                 $errors[] = Yii::t('module', 'A module with that name already exists');
2380:             }
2381:             if (empty($errors)) {
2382:                 $dirFlag = false;
2383:                 $configFlag = false;
2384:                 $tableFlag = false;
2385:                 try {
2386:                     $this->createSkeletonDirectories($moduleName, $title);
2387:                     $dirFlag = true; // Try to create the fileset
2388:                     $this->writeConfig($title, $moduleName, $recordName);
2389:                     $configFlag = true; // Write the configuration
2390:                     $this->createNewTable($moduleName);
2391:                     $tableFlag = true; // Create the DB table
2392:                 } catch (Exception $e) {
2393:                     /*
2394:                      * If any of the operations in the try block fail, we need
2395:                      * to roll back whatever successfully happened before that.
2396:                      * The flag variables below indicate which rollback operations
2397:                      * to take.
2398:                      */
2399:                     if ($dirFlag) {
2400:                         FileUtil::rrmdir('protected/modules/' . $moduleName);
2401:                     } else {
2402:                         $errors[] = Yii::t('module', 'Unable to create custom module directory.');
2403:                     }
2404:                     if ($configFlag) {
2405:                         // Nothing, already taken care of by the file delete above
2406:                     } elseif ($dirFlag) {
2407:                         $errors[] = Yii::t('module', 'Unable to create config file for custom module.');
2408:                     }
2409:                     if ($tableFlag) {
2410:                         $this->deleteTable($moduleName);
2411:                     } elseif ($dirFlag && $configFlag) {
2412:                         $errors[] = Yii::t('module', 'Unable to create table for custom module.');
2413:                     }
2414:                 }
2415:                 if (empty($errors)) {
2416:                     $moduleRecord = new Modules;
2417:                     $moduleRecord->name = $moduleName;
2418:                     $moduleRecord->title = $title;
2419:                     $moduleRecord->custom = 1;
2420:                     $moduleRecord->visible = 1;
2421:                     $moduleRecord->editable = $_POST['editable'];
2422:                     $moduleRecord->adminOnly = $_POST['adminOnly'];
2423:                     $moduleRecord->searchable = $_POST['searchable'];
2424:                     $moduleRecord->toggleable = 1;
2425:                     $moduleRecord->menuPosition = Modules::model()->count();
2426:                     $moduleRecord->save();
2427: 
2428:                     Yii::import('application.modules.' . $moduleName . '.models.*');
2429:                     $layoutModel = new FormLayout;
2430:                     $layoutModel->model = ucfirst($moduleName);
2431:                     $layoutModel->version = "Default";
2432:                     $layoutModel->layout = X2Model::getDefaultFormLayout($moduleName);
2433:                     $layoutModel->createDate = time();
2434:                     $layoutModel->lastUpdated = time();
2435:                     $layoutModel->defaultView = true;
2436:                     $layoutModel->defaultForm = true;
2437:                     $layoutModel->save();
2438: 
2439:                     $this->redirect(array('/' . $moduleName . '/index'));
2440:                 }
2441:             }
2442:         }
2443: 
2444:         $this->render('createModule', array('errors' => $errors));
2445:     }
2446: 
2447:     /**
2448:      * Creates a table for a new module
2449:      *
2450:      * This method is called by {@link AdminController::actionCreateModule} as part
2451:      * of creating a new module.  This creates the table for the new module as well
2452:      * as creating records in the x2_fields table for use in the studio.
2453:      *
2454:      * @param string $moduleName The name of the module being created
2455:      */
2456:     private function createNewTable($moduleName) {
2457:         $moduleTitle = ucfirst($moduleName);
2458:         $sqlList = array("CREATE TABLE x2_" . $moduleName . "(
2459:             id INT NOT NULL AUTO_INCREMENT primary key,
2460:             assignedTo VARCHAR(250),
2461:             name VARCHAR(250) NOT NULL,
2462:             nameId VARCHAR(250) DEFAULT NULL,
2463:             description TEXT,
2464:             createDate INT,
2465:             lastUpdated INT,
2466:             updatedBy VARCHAR(250),
2467:             UNIQUE(nameId)
2468:             ) COLLATE = utf8_general_ci",
2469:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, readOnly, keyType) VALUES ('$moduleTitle', 'id', 'ID', '0', '1','PRI')",
2470:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, readOnly, keyType) VALUES ('$moduleTitle', 'nameId', 'nameId', '0', '1','FIX')",
2471:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, type, required) VALUES ('$moduleTitle', 'name', 'Name', '0', 'varchar', '1')",
2472:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, type) VALUES ('$moduleTitle', 'assignedTo', 'Assigned To', '0', 'assignment')",
2473:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, type) VALUES ('$moduleTitle', 'description', 'Description', '0', 'text')",
2474:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, type, readOnly) VALUES ('$moduleTitle', 'createDate', 'Create Date', '0', 'date', '1')",
2475:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, type, readOnly) VALUES ('$moduleTitle', 'lastUpdated', 'Last Updated', '0', 'date', '1')",
2476:             "INSERT INTO x2_fields (modelName, fieldName, attributeLabel, custom, type, readOnly) VALUES ('$moduleTitle', 'updatedBy', 'Updated By', '0', 'assignment', '1')");
2477:         foreach ($sqlList as $sql) {
2478:             $command = Yii::app()->db->createCommand($sql);
2479:             $command->execute();
2480:         }
2481:         $this->createDefaultModulePermissions($moduleTitle);
2482:     }
2483: 
2484:     /**
2485:      * Private helper method to create initial permissions structure when
2486:      * creating a new custom module or importing one
2487:      * @param string $moduleName Name of the module
2488:      */
2489:     private function createDefaultModulePermissions($moduleName) {
2490:         $auth = Yii::app()->authManager;
2491:         $authRule = 'return $this->checkAssignment($params);';
2492:         $guestSite = $auth->getAuthItem('GuestSiteFunctionsTask');
2493:         $auth->createOperation($moduleName . 'GetItems');  // Guest Access
2494:         $auth->createOperation($moduleName . 'View');  // Read Only
2495:         $auth->createOperation($moduleName . 'Create');  // Basic Access
2496:         $auth->createOperation($moduleName . 'Update');  // Update Access
2497:         $auth->createOperation($moduleName . 'Index');  // Minimum Requirements
2498:         $auth->createOperation($moduleName . 'Admin');  // Admin Access
2499:         $auth->createOperation($moduleName . 'Delete');  // Full Access
2500:         $auth->createOperation($moduleName . 'GetTerms');  // Minimum Requirements
2501:         $auth->createOperation($moduleName . 'DeleteNote');  // Full Access
2502:         $auth->createOperation($moduleName . 'Search');  // Minimum Requirements
2503: 
2504:         $auth->createOperation($moduleName . 'MobileView'); 
2505:         $auth->createOperation($moduleName . 'MobileCreate'); 
2506:         $auth->createOperation($moduleName . 'MobileUpdate'); 
2507:         $auth->createOperation($moduleName . 'MobileDelete'); 
2508:         $auth->createOperation($moduleName . 'QuickView');  
2509:         $auth->createOperation($moduleName . 'MobileIndex');  
2510:         $auth->createOperation($moduleName . 'GetX2ModelInput');  
2511:         $auth->createOperation($moduleName . 'AjaxGetModelAutocomplete');  
2512:         $auth->createOperation($moduleName . 'X2GridViewMassAction');  
2513:         $auth->createOperation($moduleName . 'InlineEmail');  
2514: 
2515:         // Access Group Definitions
2516:         $roleAdminAccess = $auth->createTask($moduleName . 'AdminAccess');
2517:         $roleFullAccess = $auth->createTask($moduleName . 'FullAccess');
2518:         $rolePrivateFullAccess = $auth->createTask($moduleName . 'PrivateFullAccess');
2519:         $roleUpdateAccess = $auth->createTask($moduleName . 'UpdateAccess');
2520:         $rolePrivateUpdateAccess = $auth->createTask($moduleName . 'PrivateUpdateAccess');
2521:         $roleBasicAccess = $auth->createTask($moduleName . 'BasicAccess');
2522:         $roleReadOnlyAccess = $auth->createTask($moduleName . 'ReadOnlyAccess');
2523:         $rolePrivateReadOnlyAccess = $auth->createTask($moduleName . 'PrivateReadOnlyAccess');
2524:         $roleMinimumRequirements = $auth->createTask($moduleName . 'MinimumRequirements');
2525: 
2526:         // Private Task Definitions
2527:         $rolePrivateDelete = $auth->createTask($moduleName . 'DeletePrivate', 'Delete their own records', $authRule);
2528:         $rolePrivateDelete->addChild($moduleName . 'Delete');
2529:         $rolePrivateDelete->addChild($moduleName . 'DeleteNote');
2530:         $rolePrivateDelete->addChild($moduleName . 'MobileDelete');
2531:         $rolePrivateUpdate = $auth->createTask($moduleName . 'UpdatePrivate', 'Update their own records', $authRule);
2532:         $rolePrivateUpdate->addChild($moduleName . 'Update');
2533:         $rolePrivateUpdate->addChild ($moduleName.'MobileUpdate');
2534:         $rolePrivateView = $auth->createTask($moduleName . 'ViewPrivate', 'View their own record', $authRule);
2535:         $rolePrivateView->addChild($moduleName . 'View');
2536:         $rolePrivateView->addChild($moduleName . 'MobileView');
2537: 
2538:         // Guest Requirements
2539:         $guestSite->addChild($moduleName . 'GetItems');
2540: 
2541:         // Minimum Requirements
2542:         $roleMinimumRequirements->addChild($moduleName . 'Index');
2543:         $roleMinimumRequirements->addChild($moduleName . 'MobileIndex');
2544:         $roleMinimumRequirements->addChild($moduleName . 'GetTerms');
2545:         $roleMinimumRequirements->addChild($moduleName . 'Search');
2546:         $roleMinimumRequirements->addChild($moduleName . 'AjaxGetModelAutocomplete');
2547:         $roleMinimumRequirements->addChild($moduleName . 'X2GridViewMassAction');
2548: 
2549:         // Read Only
2550:         $roleReadOnlyAccess->addChild($moduleName . 'MinimumRequirements');
2551:         $roleReadOnlyAccess->addChild($moduleName . 'View');
2552:         $roleReadOnlyAccess->addChild($moduleName . 'MobileView');
2553:         $roleReadOnlyAccess->addChild($moduleName . 'QuickView');
2554:         $roleReadOnlyAccess->addChild($moduleName . 'InlineEmail');
2555: 
2556:         // Private Read Only
2557:         $rolePrivateReadOnlyAccess->addChild($moduleName . 'MinimumRequirements');
2558:         $rolePrivateReadOnlyAccess->addChild($moduleName . 'ViewPrivate');
2559:         $rolePrivateReadOnlyAccess->addChild($moduleName . 'QuickView');
2560:         $rolePrivateReadOnlyAccess->addChild($moduleName . 'InlineEmail');
2561: 
2562:         // Basic Access
2563:         $roleBasicAccess->addChild($moduleName . 'MinimumRequirements');
2564:         $roleBasicAccess->addChild($moduleName . 'Create');
2565:         $roleBasicAccess->addChild($moduleName . 'MobileCreate');
2566: 
2567:         // Update Access
2568:         $roleUpdateAccess->addChild($moduleName . 'MinimumRequirements');
2569:         $roleUpdateAccess->addChild($moduleName . 'Update');
2570:         $roleUpdateAccess->addChild($moduleName . 'MobileUpdate');
2571:         $roleUpdateAccess->addChild($moduleName . 'GetX2ModelInput');
2572: 
2573:         // Private Update Access
2574:         $rolePrivateUpdateAccess->addChild($moduleName . 'MinimumRequirements');
2575:         $rolePrivateUpdateAccess->addChild($moduleName . 'UpdatePrivate');
2576:         $rolePrivateUpdateAccess->addChild($moduleName . 'GetX2ModelInput');
2577: 
2578:         // Full Access
2579:         $roleFullAccess->addChild($moduleName . 'MinimumRequirements');
2580:         $roleFullAccess->addChild($moduleName . 'Delete');
2581:         $roleFullAccess->addChild($moduleName . 'MobileDelete');
2582:         $roleFullAccess->addChild($moduleName . 'DeleteNote');
2583: 
2584:         // Private Full Access
2585:         $rolePrivateFullAccess->addChild($moduleName . 'MinimumRequirements');
2586:         $rolePrivateFullAccess->addChild($moduleName . 'DeletePrivate');
2587: 
2588:         // Admin Access
2589:         $roleAdminAccess->addChild($moduleName . 'MinimumRequirements');
2590:         $roleAdminAccess->addChild($moduleName . 'Admin');
2591: 
2592:         // Assign the permissions to roles
2593:         $defaultRole = $auth->getAuthItem('DefaultRole');
2594:         $defaultRole->removeChild($moduleName . 'Index');
2595:         $defaultRole->addChild($moduleName . 'UpdateAccess');
2596:         $defaultRole->addChild($moduleName . 'BasicAccess');
2597:         $defaultRole->addChild($moduleName . 'ReadOnlyAccess');
2598:         $defaultRole->addChild($moduleName . 'MinimumRequirements');
2599: 
2600:         $adminRole = $auth->getAuthItem('administrator');
2601:         $adminRole->removeChild($moduleName . 'Admin');
2602:         $adminRole->addChild($moduleName . 'AdminAccess');
2603:         $adminRole->addChild($moduleName . 'FullAccess');
2604:         $adminRole->addChild($moduleName . 'UpdateAccess');
2605:         $adminRole->addChild($moduleName . 'PrivateUpdateAccess');
2606:         $adminRole->addChild($moduleName . 'BasicAccess');
2607:         $adminRole->addChild($moduleName . 'ReadOnlyAccess');
2608:         $adminRole->addChild($moduleName . 'MinimumRequirements');
2609:     }
2610: 
2611:     /**
2612:      * Cleanup operation for custom modules. This is run on deletion to remove
2613:      * the database table.
2614:      * @param string $moduleName The name of the module being deleted
2615:      */
2616:     private function deleteTable($moduleName) {
2617:         $moduleTitle = ucfirst($moduleName);
2618:         $ucName = $moduleTitle;
2619:         $sqlList = array(
2620:             'DROP TABLE IF EXISTS `x2_' . $moduleName . '`',
2621:             'DELETE FOM x2_fields WHERE modelName="' . $moduleTitle . '"',
2622:         );
2623:         foreach ($sqlList as $sql) {
2624:             $command = Yii::app()->db->createCommand($sql);
2625:             $command->execute();
2626:         }
2627:         $auth = Yii::app()->authManager;
2628:         $auth->removeAuthItem($ucName . 'GetItems');
2629:         $auth->removeAuthItem($ucName . 'View');
2630:         $auth->removeAuthItem($ucName . 'Create');
2631:         $auth->removeAuthItem($ucName . 'Update');
2632:         $auth->removeAuthItem($ucName . 'Index');
2633:         $auth->removeAuthItem($ucName . 'Admin');
2634:         $auth->removeAuthItem($ucName . 'Delete');
2635:         $auth->removeAuthItem($ucName . 'GetTerms');
2636:         $auth->removeAuthItem($ucName . 'DeleteNote');
2637:         $auth->removeAuthItem($ucName . 'Search');
2638:         $auth->removeAuthItem($ucName . 'AdminAccess');
2639:         $auth->removeAuthItem($ucName . 'FullAccess');
2640:         $auth->removeAuthItem($ucName . 'PrivateFullAccess');
2641:         $auth->removeAuthItem($ucName . 'UpdateAccess');
2642:         $auth->removeAuthItem($ucName . 'PrivateUpdateAccess');
2643:         $auth->removeAuthItem($ucName . 'BasicAccess');
2644:         $auth->removeAuthItem($ucName . 'ReadOnlyAccess');
2645:         $auth->removeAuthItem($ucName . 'PrivateReadOnlyAccess');
2646:         $auth->removeAuthItem($ucName . 'MinimumRequirements');
2647:     }
2648: 
2649:     /**
2650:      * Create file system for a custom module
2651:      *
2652:      * This method is called by {@link AdminController::actionCreateModule} as a
2653:      * part of creating a new module.  This method copies all the proper files to
2654:      * their new directories, renames them, and replaces the contents to fit the
2655:      * new module name.
2656:      *
2657:      * @param string $moduleName The name of the module being created
2658:      * @param string $moduleTitle The title of the module being created
2659:      */
2660:     private function createSkeletonDirectories($moduleName, $moduleTitle) {
2661: 
2662:         $errors = array();
2663: 
2664:         $templateFolderPath = 'protected/modules/template/';
2665:         $moduleFolderPath = 'protected/modules/' . $moduleName . '/';
2666: 
2667:         $moduleFolder = Yii::app()->file->set($moduleFolderPath);
2668:         if (!$moduleFolder->exists && $moduleFolder->createDir() === false)
2669:             throw new Exception('Error creating module folder "' . $moduleFolderPath . '".');
2670: 
2671:         if (Yii::app()->file->set($templateFolderPath)->copy($moduleName) === false)
2672:             throw new Exception('Error copying Template folder "' . $templateFolderPath . '".');
2673: 
2674:         // list of files to process
2675:         $fileNames = array(
2676:             'register.php',
2677:             'templatesConfig.php',
2678:             'TemplatesModule.php',
2679:             'controllers/TemplatesController.php',
2680:             'data/install.sql',
2681:             'data/uninstall.sql',
2682:             'models/Templates.php',
2683:             'views/templates/_search.php',
2684:             'views/templates/_view.php',
2685:             'views/templates/admin.php',
2686:             'views/templates/create.php',
2687:             'views/templates/index.php',
2688:             'views/templates/update.php',
2689:             'views/templates/view.php',
2690:         );
2691: 
2692:         foreach ($fileNames as $fileName) {
2693:             // calculate proper file name
2694:             $fileName = $moduleFolderPath . $fileName;
2695: 
2696:             $file = Yii::app()->file->set($fileName);
2697:             if (!$file->exists)
2698:                 throw new Exception('Unable to find template file "' . $fileName . '".');
2699: 
2700:             // rename files
2701:             $newFileName = str_replace(array('templates', 'Templates'), array($moduleName, ucfirst($moduleName)), $file->filename);
2702:             if ($file->setFileName($newFileName) === false)
2703:                 throw new Exception('Error renaming template file "' . $fileName . '" to "' . $newFileName . '".');
2704: 
2705:             // chmod($file->filename, 0755);
2706:             // $file->setPermissions(0755);
2707:             // replace "template", "Templates", etc within the file
2708:             $contents = $file->getContents();
2709:             $contents = str_replace(array('templates', 'TemplatesTitle', 'Templates'), array($moduleName, $moduleTitle, ucfirst($moduleName)), $contents);
2710: 
2711:             if ($file->setContents($contents) === false)
2712:                 throw new Exception('Error modifying template file "' . $newFileName . '".');
2713:         }
2714:         if (!is_dir('protected/modules/' . $moduleName . '/views/' . $moduleName)) {
2715:             rename('protected/modules/' . $moduleName . '/views/templates', 'protected/modules/' . $moduleName . '/views/' . $moduleName);
2716:         } else {
2717:             FileUtil::ccopy('protected/modules/' . $moduleName . '/views/templates', 'protected/modules/' . $moduleName . '/views/' . $moduleName);
2718:         }
2719:     }
2720: 
2721:     /**
2722:      * Create module config file
2723:      *
2724:      * This is called by {@link AdminController::actionCreateModule} in the process
2725:      * of creating a new module.  This writes a config file for the module to use.
2726:      *
2727:      * @param string $title The display title of the module
2728:      * @param string $moduleName The actual name of the module
2729:      * @param string $recordName What to call the records of this module
2730:      */
2731:     private function writeConfig($title, $moduleName, $recordName) {
2732: 
2733:         $configFilePath = 'protected/modules/' . $moduleName . '/' . $moduleName . 'Config.php';
2734:         $configFile = Yii::app()->file->set($configFilePath, true);
2735: 
2736:         $contents = str_replace(
2737:                 array(
2738:             '{title}',
2739:             '{moduleName}',
2740:             '{recordName}',
2741:                 ), array(
2742:             addslashes($title),
2743:             addslashes($moduleName),
2744:             addslashes($recordName),
2745:                 ), $configFile->getContents()
2746:         );
2747: 
2748:         if ($configFile->setContents($contents) === false)
2749:             throw new Exception('Error writing to config file "' . $configFilePath . '".');
2750:     }
2751: 
2752:     /**
2753:      * Deletes a custom module.
2754:      *
2755:      * This method deletes an admin created module from the system.  All files are
2756:      * deleted as well as the table associated with it.
2757:      */
2758:     public function actionDeleteModule() {
2759:         if (isset($_POST['name'])) {
2760:             $moduleName = $_POST['name'];
2761:             $module = Modules::model()->findByPk($moduleName);
2762:             $moduleName = $module->name;
2763:             if (isset($module)) {
2764:                 if ($module->name != 'document' && $module->delete())
2765:                     $this->deleteModuleData($moduleName);
2766:                 else
2767:                     $module->delete();
2768:             }
2769:             $this->redirect(array('/admin/index'));
2770:         }
2771: 
2772:         $arr = array();
2773:         $modules = Modules::model()->findAllByAttributes(array('toggleable' => 1));
2774:         foreach ($modules as $item) {
2775:             $arr[$item->id] = $item->title;
2776:         }
2777: 
2778:         $this->render('deleteModule', array(
2779:             'modules' => $arr,
2780:         ));
2781:     }
2782: 
2783:     /**
2784:      * Helper function to remove the files and SQL data associated with a module
2785:      * @param string $moduleName Name of the module to delete
2786:      */
2787:     private function deleteModuleData($moduleName) {
2788:         $registerFile = 'protected/modules/' . $moduleName . '/register.php';
2789:         if (!is_file ($registerFile))
2790:             return;
2791:         $config = include($registerFile);
2792:         $uninstall = $config['uninstall'];
2793:         if (isset($config['version'])) {
2794:             foreach ($uninstall as $sql) {
2795:                 // New convention:
2796:                 // If element is a string, treat as a path to an SQL script file.
2797:                 // Otherwise, if array, treat as a list of SQL commands to run.
2798:                 $sqlComm = $sql;
2799:                 if (is_string($sql)) {
2800:                     if (file_exists($sql)) {
2801:                         $sqlComm = explode('/*&*/', file_get_contents($sql));
2802:                     }
2803:                 }
2804:                 foreach ($sqlComm as $sqlLine) {
2805:                     $query = Yii::app()->db->createCommand($sqlLine);
2806:                     try {
2807:                         $query->execute();
2808:                     } catch (CDbException $e) {
2809:                         
2810:                     }
2811:                 }
2812:             }
2813:         } else {
2814:             // The old way, for backwards compatibility:
2815:             foreach ($uninstall as $sql) {
2816:                 $query = Yii::app()->db->createCommand($sql);
2817:                 $query->execute();
2818:             }
2819:         }
2820:         X2Model::model('Fields')->deleteAllByAttributes(array('modelName' => ucfirst($moduleName)));
2821:         X2Model::model('Fields')->updateAll(array('linkType' => null, 'type' => 'varchar'), "linkType='$moduleName'");
2822:         X2Model::model('FormLayout')->deleteAllByAttributes(array('model' => $moduleName));
2823:         X2Model::model('Relationships')->deleteAll('firstType = :model OR secondType = :model', array(':model' => $moduleName));
2824:         $auth = Yii::app()->authManager;
2825:         $ucName = ucfirst($moduleName);
2826:         $auth->removeAuthItem($ucName . 'GetItems');
2827:         $auth->removeAuthItem($ucName . 'View');
2828:         $auth->removeAuthItem($ucName . 'Create');
2829:         $auth->removeAuthItem($ucName . 'Update');
2830:         $auth->removeAuthItem($ucName . 'Index');
2831:         $auth->removeAuthItem($ucName . 'Admin');
2832:         $auth->removeAuthItem($ucName . 'Delete');
2833:         $auth->removeAuthItem($ucName . 'GetTerms');
2834:         $auth->removeAuthItem($ucName . 'DeleteNote');
2835:         $auth->removeAuthItem($ucName . 'Search');
2836:         $auth->removeAuthItem($ucName . 'AdminAccess');
2837:         $auth->removeAuthItem($ucName . 'FullAccess');
2838:         $auth->removeAuthItem($ucName . 'PrivateFullAccess');
2839:         $auth->removeAuthItem($ucName . 'UpdateAccess');
2840:         $auth->removeAuthItem($ucName . 'PrivateUpdateAccess');
2841:         $auth->removeAuthItem($ucName . 'BasicAccess');
2842:         $auth->removeAuthItem($ucName . 'ReadOnlyAccess');
2843:         $auth->removeAuthItem($ucName . 'PrivateReadOnlyAccess');
2844:         $auth->removeAuthItem($ucName . 'MinimumRequirements');
2845:         $auth->removeAuthItem($ucName . 'ViewPrivate');
2846:         $auth->removeAuthItem($ucName . 'UpdatePrivate');
2847:         $auth->removeAuthItem($ucName . 'DeletePrivate');
2848: 
2849:         $auth->removeAuthItem($ucName . 'MobileView');
2850:         $auth->removeAuthItem($ucName . 'QuickView');
2851:         $auth->removeAuthItem($ucName . 'MobileIndex');
2852:         $auth->removeAuthItem($ucName . 'MobileCreate');
2853:         $auth->removeAuthItem($ucName . 'MobileDelete');
2854:         $auth->removeAuthItem($ucName . 'MobileUpdate');
2855:         $auth->removeAuthItem($ucName . 'GetX2ModelInput');
2856:         $auth->removeAuthItem($ucName . 'AjaxGetModelAutocomplete');
2857:         $auth->removeAuthItem($ucName . 'X2GridViewMassAction');
2858:         $auth->removeAuthItem($ucName . 'InlineEmail');
2859: 
2860:         // Remove related Summary widgets
2861:         foreach (Profile::model()->findAll () as $profile) {
2862:             $settings = $profile->getProfileWidgetLayout();
2863:             foreach ($settings as $key => $data)
2864:                 if (isset ($data['modelType']) && $data['modelType'] === $ucName)
2865:                     $settings[$key] = null;
2866:             $profile->setProfileWidgetLayout($settings);
2867:             $profile->save();
2868:         }
2869: 
2870:         FileUtil::rrmdir('protected/modules/' . $moduleName);
2871:     }
2872: 
2873:     /**
2874:      * Export the mapping from the model importer
2875:      */
2876:     public function actionExportMapping() {
2877:         $model = $_POST['model'];
2878:         $name = (isset($_POST['name']) && !empty($_POST['name'])) ? $_POST['name'] : "Unknown";
2879:         $name .= " to X2Engine " . Yii::app()->params->version;
2880:         $filename = (isset($_POST['name']) && !empty($_POST['name'])) ? lcfirst($_POST['name']) : "importMapping";
2881:         $keys = $_POST['keys'];
2882:         $attributes = $_POST['attributes'];
2883:         $mappingResult = $this->verifyImportMap ($model, $keys, $attributes, true);
2884:         if (!empty($_SESSION['importMap'])) {
2885:             $map = array(
2886:                 'name' => $name,
2887:                 'mapping' => $_SESSION['importMap']
2888:             );
2889:             $mapFile = fopen ($this->safePath($filename . '.json'), 'w');
2890:             fwrite ($mapFile, CJSON::encode($map));
2891:             fclose ($mapFile);
2892:             $mappingResult['map_filename'] = $filename. '.json';
2893:         }
2894:         echo CJSON::encode ($mappingResult);
2895:         Yii::app()->end();
2896:     }
2897: 
2898:     /**
2899:      * Export records from a model
2900:      */
2901:     public function actionExportModels($listId = null) {
2902:         unset($_SESSION['modelExportFile'], $_SESSION['exportModelCriteria'],
2903:             $_SESSION['modelExportMeta'], $_SESSION['exportModelListId']);
2904:         $modelList = Modules::getExportableModules();
2905:         // Determine the model selected by the user
2906:         if (isset($_GET['model']) || isset($_POST['model'])) {
2907:             $model = (isset($_GET['model'])) ? $_GET['model'] : $_POST['model'];
2908:             $modelName = str_replace(' ', '', $model);
2909:         }
2910:         if (isset($model) && in_array($modelName, array_keys($modelList))) {
2911:             $staticModel = X2Model::model($modelName);
2912:             $modulePath = '/' . $staticModel->module;
2913:             $modulePath .= $modulePath;
2914:             if (is_null($listId) || $model != 'Contacts') {
2915:                 $file = "records_export.csv";
2916:                 $listName = CHtml::link(
2917:                                 Yii::t('admin', 'All {model}', array(
2918:                                     '{model}' => $model)), array($modulePath . '/index'), array('style' => 'text-decoration:none;')
2919:                 );
2920: 
2921:                 // Forcefully disable eager loading so it doesn't go super-slow)
2922:                 $_SESSION['exportModelCriteria'] = new CDbCriteria();
2923:                 $_SESSION['exportModelCriteria']->with = array();
2924:             } else {
2925:                 $list = X2List::load($listId);
2926:                 $_SESSION['exportModelListId'] = $listId;
2927:                 $_SESSION['exportModelCriteria'] = $list->queryCriteria();
2928:                 $file = "list" . $listId . ".csv";
2929:                 $listName = CHtml::link(Yii::t('admin', 'List') . " $listId: " . $list->name, array($modulePath . '/list', 'id' => $listId), array('style' => 'text-decoration:none;'));
2930:             }
2931:             $_SESSION['modelExportFile'] = $file;
2932:         } else {
2933:             // If an invalid model was chosen, unset it so that the model list
2934:             // will be displayed instead.
2935:             if (isset($model))
2936:                 unset($model);
2937:         }
2938: 
2939:         $viewParam = array(
2940:             'modelList' => $modelList,
2941:             'listId' => $listId,
2942:             'model' => '',
2943:             'modelDisplayName' => isset($model)?Modules::displayName (true, $model):'',
2944:         );
2945:         if (isset($model)) {
2946:             $viewParam['model'] = $model;
2947:             if ($model == 'Contacts') {
2948:                 $viewParam['listName'] = $listName;
2949:             } else if (in_array ($modelName, array('Quote', 'Product'))) {
2950:                 $viewParam['modelDisplayName'] = Modules::displayName (true, $model.'s');
2951:             } else if ($modelName === 'Campaign') {
2952:                 $viewParam['modelDisplayName'] = Yii::t ('common', 'Campaigns');
2953:             } else if ($modelName === 'Opportunity') {
2954:                 $viewParam['modelDisplayName'] = Modules::displayName (true, 'Opportunities');
2955:             }
2956:         }
2957: 
2958:         $this->render('exportModels', $viewParam);
2959:     }
2960: 
2961:     /**
2962:      * An AJAX called function which exports data to a CSV via pagination.
2963:      * This is a generalized version of the Contacts export.
2964:      * @param int $page The page of the data provider to export
2965:      */
2966:     public function actionExportModelRecords($page, $model) {
2967:         X2Model::$autoPopulateFields = false;
2968:         $file = $this->safePath($_SESSION['modelExportFile']);
2969:         $staticModel = X2Model::model(str_replace(' ', '', $model));
2970:         $fields = $staticModel->getFields();
2971:         $fp = fopen($file, 'a+');
2972: 
2973:         // Load data provider based on export criteria
2974:         $excludeHidden = !isset($_GET['includeHidden']) || $_GET['includeHidden'] === 'false';
2975:         if ($page == 0 && $excludeHidden && isset($_SESSION['exportModelCriteria']) &&
2976:                 ($_SESSION['exportModelCriteria'] instanceof CDbCriteria)) {
2977: 
2978:             // Save hidden condition in criteria
2979:             $hiddenConditions = $staticModel->getHiddenCondition();
2980:             $_SESSION['exportModelCriteria']->addCondition($hiddenConditions);
2981:         }
2982:         $dp = new CActiveDataProvider($model, array(
2983:             'criteria' => isset($_SESSION['exportModelCriteria']) ? $_SESSION['exportModelCriteria'] : array(),
2984:             'pagination' => array(
2985:                 'pageSize' => 100,
2986:             ),
2987:         ));
2988:         // Flip through to the right page.
2989:         $pg = $dp->getPagination();
2990:         $pg->setCurrentPage($page);
2991:         $dp->setPagination($pg);
2992:         $records = $dp->getData();
2993:         $pageCount = $dp->getPagination()->getPageCount();
2994: 
2995:         // We need to set our data to be human friendly, so loop through all the
2996:         // records and format any date / link / visibility fields.
2997:         foreach ($records as $record) {
2998:             foreach ($fields as $field) {
2999:                 $fieldName = $field->fieldName;
3000:                 if ($field->type == 'date' || $field->type == 'dateTime') {
3001:                     if (is_numeric($record->$fieldName))
3002:                         $record->$fieldName = Formatter::formatLongDateTime($record->$fieldName);
3003:                 }elseif ($field->type == 'link') {
3004:                     $name = $record->$fieldName;
3005:                     if (!empty($field->linkType)) {
3006:                         list($name, $id) = Fields::nameAndId($name);
3007:                     }
3008:                     if (!empty($name))
3009:                         $record->$fieldName = $name;
3010:                 }elseif ($fieldName == 'visibility') {
3011:                     switch ($record->$fieldName) {
3012:                         case 0:
3013:                             $record->$fieldName = 'Private';
3014:                             break;
3015:                         case 1:
3016:                             $record->$fieldName = 'Public';
3017:                             break;
3018:                         case 2:
3019:                             $record->$fieldName = 'User\'s Groups';
3020:                             break;
3021:                         default:
3022:                             $record->$fieldName = 'Private';
3023:                     }
3024:                 }
3025:             }
3026:             // Enforce metadata to ensure accuracy of column order, then export.
3027:             $combinedMeta = array_combine($_SESSION['modelExportMeta'], $_SESSION['modelExportMeta']);
3028:             $recordAttributes = $record->attributes;
3029:             if ($model === 'Actions') {
3030:                 // Export descriptions with Actions
3031:                 $actionText = $record->actionText;
3032:                 if ($actionText) {
3033:                     $actionDescription = $actionText->text;
3034:                     $recordAttributes = array_merge($recordAttributes, array(
3035:                         'actionDescription' => $actionDescription
3036:                     ));
3037:                 }
3038:             }
3039:             if ($_SESSION['includeTags']) {
3040:                 $tags= $record->getTags();
3041:                 $recordAttributes = array_merge ($recordAttributes, array(
3042:                     'tags' => implode(',', $tags),
3043:                 ));
3044:             }
3045:             $tempAttributes = array_intersect_key($recordAttributes, $combinedMeta);
3046:             $tempAttributes = array_merge($combinedMeta, $tempAttributes);
3047:             fputcsv($fp, $tempAttributes, $this->importDelimeter, $this->importEnclosure);
3048:         }
3049: 
3050:         unset($dp);
3051: 
3052:         fclose($fp);
3053:         if ($page + 1 < $pageCount) {
3054:             $this->respond (CJSON::encode(array(
3055:                 'page' => $page + 1
3056:             )));
3057:         } else {
3058:             $success = $this->prepareExportDeliverable ($file, $_SESSION['exportFormat']);
3059:             if ($_SESSION['exportFormat']['exportDestination'] === 'download') {
3060:                 if ($_SESSION['exportFormat']['compressOutput']) {
3061:                     $_SESSION['modelExportFile'] = $this->adjustExportPath (
3062:                         $_SESSION['modelExportFile'],
3063:                         $_SESSION['exportFormat']
3064:                     );
3065:                 }
3066:             } else {
3067:                 $_SESSION['modelExportFile'] = '';
3068:             }
3069:             unset ($_SESSION['exportFormat']);
3070: 
3071:             $this->respond (CJSON::encode(array(
3072:                 'success' => $success,
3073:                 'dlUrl' => $_SESSION['modelExportFile'],
3074:             )));
3075:         }
3076:     }
3077: 
3078:     protected function generateModuleSqlData($moduleName) {
3079:         $sql = "";
3080:         $disallow = array(
3081:             "id",
3082:             "assignedTo",
3083:             "name",
3084:             "nameId",
3085:             "description",
3086:             "createDate",
3087:             "lastUpdated",
3088:             "updatedBy",
3089:         );
3090: 
3091:         $fields = Fields::model()->findAllByAttributes(array(
3092:             'modelName' => ucfirst($moduleName)
3093:         ));
3094:         foreach ($fields as $field) {
3095:             if (array_search($field->fieldName, $disallow) === false) {
3096:                 $fieldType = $field->type;
3097:                 $columnDefinitions = Fields::getFieldTypes('columnDefinition');
3098:                 if (isset($columnDefinitions[$fieldType])) {
3099:                     $fieldType = $columnDefinitions[$fieldType];
3100:                 } else {
3101:                     $fieldType = 'VARCHAR(250)';
3102:                 }
3103: 
3104:                 $linkType = $field->linkType;
3105:                 if ($field->type === 'dropdown') {
3106:                     // Export associated dropdown values
3107:                     $dropdown = Dropdowns::model()->findByPk($field->linkType);
3108:                     if ($dropdown) {
3109:                         $sql .= "/*&*/INSERT INTO x2_dropdowns " .
3110:                                 "(name, options, multi, parent, parentVal) " .
3111:                                 "VALUES " .
3112:                                 "('$dropdown->name', '$dropdown->options', '$dropdown->multi', " .
3113:                                 "'$dropdown->parent', '$dropdown->parentVal');";
3114:                         // Temporarily set the linkType to the dropdowns name: this is to avoid
3115:                         // messy ID conflicts when importing a module to existing installations
3116:                         $linkType = $dropdown->name;
3117:                     }
3118:                 }
3119: 
3120:                 $sql .= "/*&*/ALTER TABLE x2_$moduleName ADD COLUMN $field->fieldName $fieldType;";
3121:                 $sql .= "/*&*/INSERT INTO x2_fields " .
3122:                         "(modelName, fieldName, attributeLabel, modified, custom, type, linkType) " .
3123:                         "VALUES " .
3124:                         "('$moduleName', '$field->fieldName', '$field->attributeLabel', '1', '1', " .
3125:                         "'$field->type', '$linkType');";
3126:             }
3127:         }
3128:         $formLayouts = X2Model::model('FormLayout')->findAllByAttributes(array(
3129:             'model' => $moduleName
3130:         ));
3131:         foreach ($formLayouts as $layout) {
3132:             $attributes = $layout->attributes;
3133:             unset($attributes['id']);
3134:             $attributeKeys = array_keys($attributes);
3135:             $attributeValues = array_values($attributes);
3136:             $keys = implode(", ", $attributeKeys);
3137:             $values = "'" . implode("', '", $attributeValues) . "'";
3138:             $sql.="/*&*/INSERT INTO x2_form_layouts ($keys) VALUES ($values);";
3139:         }
3140:         return $sql;
3141:     }
3142: 
3143:     /**
3144:      * Export a custom module.
3145:      *
3146:      * This method creates a zip file from a custom module with all the proper
3147:      * files and SQL for installation required to set up the module again.  These
3148:      * zip files can be imported into other X2 installations.
3149:      */
3150:     public function actionExportModule() {
3151:         $dlFlag = false;
3152:         if (isset($_POST['name'])) {
3153:             $module = Modules::model()->findByAttributes (array(
3154:                 'name' => $_POST['name'],
3155:             ));
3156:             if ($module) {
3157:                 $moduleName = $module->name;
3158: 
3159:                 $sql = $this->generateModuleSqlData($moduleName);
3160:                 $db = Yii::app()->file->set("protected/modules/$moduleName/sqlData.sql");
3161:                 $db->create();
3162:                 $db->setContents($sql);
3163: 
3164:                 if (file_exists($moduleName . ".zip")) {
3165:                     unlink($moduleName . ".zip");
3166:                 }
3167: 
3168:                 $zip = Yii::app()->zip;
3169:                 $zip->makeZip('protected/modules/' . $moduleName, $moduleName . ".zip");
3170:                 $dlFlag = true;
3171:             } else {
3172:                 Yii::app()->user->setFlash ('error', Yii::t('admin', 'The module you have selected no longer exists.'));
3173:             }
3174:         }
3175: 
3176:         $arr = array();
3177: 
3178:         $modules = Modules::model()->findAll();
3179:         foreach ($modules as $module) {
3180:             if ($module->custom) {
3181:                 $arr[$module->name] = $module->title;
3182:             }
3183:         }
3184: 
3185:         $this->render('exportModules', array(
3186:             'modules' => $arr,
3187:             'dlFlag' => $dlFlag? : false,
3188:             'file' => $dlFlag ? ($_POST['name']) : ''
3189:         ));
3190:     }
3191: 
3192:     /**
3193:      * The general model import page
3194:      */
3195:     public function actionImportModels() {
3196:         // Determine the model selected by the user
3197:         if (isset($_GET['model']) || isset($_POST['model'])) {
3198:             $_SESSION['model'] = (isset($_GET['model'])) ? $_GET['model'] : $_POST['model'];
3199:         }
3200:         // Retrieve specified export delimeter and enclosure
3201:         $_SESSION['delimeter'] = (isset($_POST['delimeter']) ? $_POST['delimeter'] : ',');
3202:         $_SESSION['enclosure'] = (isset($_POST['enclosure']) ? $_POST['enclosure'] : '"');
3203:         // Retrive the default map option selected by the user, otherwise DO NOT MAP by default
3204:         $defaultMapping = (isset($_POST['defaultMapOption']) ? $_POST['defaultMapOption'] : '');
3205: 
3206:         if (isset($_FILES['data'])) {
3207:             $temp = CUploadedFile::getInstanceByName('data');
3208:             $temp->saveAs($filePath = $this->safePath('data.csv'));
3209:             ini_set('auto_detect_line_endings', 1); // Account for Mac based CSVs if possible
3210:             $_SESSION['csvLength'] = 0;
3211:             if (file_exists($filePath)) {
3212:                 $fp = fopen($filePath, 'r+');
3213:                 $csvLength = $this->calculateCsvLength($filePath);
3214:                 $this->fixCsvLineEndings($filePath);
3215:             } else {
3216:                 throw new Exception('There was an error saving the models file.');
3217:             }
3218: 
3219:             list($meta, $x2attributes) = $this->initializeModelImporter($fp);
3220:             $preselectedMap = false;
3221: 
3222:             if (isset($_POST['x2maps'])) {
3223:                 // Use an existing import map from the app
3224:                 $importMap = $this->loadImportMap($_POST['x2maps']);
3225:                 if (empty($importMap)) {
3226:                     $_SESSION['errors'] = Yii::t('admin', 'Unable to load import map');
3227:                     $this->redirect('importModels');
3228:                 }
3229:                 $_SESSION['importMap'] = $this->normalizeImportMap ($importMap['mapping'], $meta);
3230:                 $_SESSION['mapName'] = $importMap['name'];
3231:                 // Make sure $importMap is consistent with and without an uploaded import map
3232:                 $importMap = $_SESSION['importMap'];
3233:                 $preselectedMap = true;
3234:             } else if (CUploadedFile::getInstanceByName('mapping') instanceof CUploadedFile && CUploadedFile::getInstanceByName('mapping')->size > 0) {
3235:                 $this->loadUploadedImportMap();
3236:                 $_SESSION['importMap'] = $this->normalizeImportMap ($_SESSION['importMap'], $meta);
3237:                 $preselectedMap = true;
3238:                 $importMap = $_SESSION['importMap'];
3239:             } else {
3240:                 // Set up import map via the internal function
3241:                 $this->createImportMap($x2attributes, $meta);
3242: 
3243:                 $importMap = $_SESSION['importMap'];
3244:                 // We need the flipped version to display to users more easily which
3245:                 // of their fields maps to what X2 field
3246:                 $importMap = array_flip($importMap);
3247:             }
3248:             $sampleRecords = $this->prepareImportSampleRecords($meta, $fp);
3249:             fclose($fp);
3250: 
3251:             // Remove the import failures column; the user doesn't need to know about it
3252:             $meta = array_filter($meta, function($x) {
3253:                 return $x !== 'X2_Import_Failures';
3254:             });
3255: 
3256:             // Retrieve link-type fields and associations to present a selector for the
3257:             // field in the related model to search on
3258:             $linkFields = Fields::model()->findAllByAttributes(array(
3259:                 'modelName' => $_SESSION['model'],
3260:                 'type' => 'link',
3261:             ));
3262:             $linkFieldModelMap = array();
3263:             foreach ($linkFields as $field) {
3264:                 $linkFieldModelMap[$field['fieldName']] = $field['linkType'];
3265:             }
3266:             $possibleModels = array_keys(Modules::getExportableModules());
3267:             foreach ($possibleModels as $model) {
3268:                 $attributes = Fields::model()->findAllByAttributes(array(
3269:                     'modelName' => $model,
3270:                 ));
3271:                 foreach ($attributes as $attr)
3272:                     $listData[$attr['fieldName']] = $attr['attributeLabel'];
3273:                 $dropdown = CHtml::dropDownList ('attr', 'name', $listData, array(
3274:                     'class' => 'linkMatchSelector',
3275:                 ));
3276:                 $linkedRecordDropdowns[$model] = $dropdown;
3277:             }
3278: 
3279:             $this->render('processModels', array(
3280:                 'defaultMapping' => $defaultMapping,
3281:                 'attributes' => $x2attributes,
3282:                 'meta' => $meta,
3283:                 'csvLength' => isset ($csvLength) ? $csvLength : null,
3284:                 'fields' => $_SESSION['fields'],
3285:                 'model' => str_replace(' ', '', $_SESSION['model']),
3286:                 'sampleRecords' => $sampleRecords,
3287:                 'importMap' => $importMap,
3288:                 'preselectedMap' => $preselectedMap,
3289:                 'linkFieldModelMap' => $linkFieldModelMap,
3290:                 'linkedRecordDropdowns' => $linkedRecordDropdowns,
3291:             ));
3292:         } else {
3293:             $modelList = Modules::getExportableModules();
3294:             $errors = (isset($_SESSION['errors']) ? $_SESSION['errors'] : "");
3295:             $this->render('importModels', array(
3296:                 'model' => isset($_SESSION['model']) ? $_SESSION['model'] : '',
3297:                 'modelList' => $modelList,
3298:                 'errors' => $errors,
3299:             ));
3300:         }
3301:     }
3302: 
3303:     /**
3304:      * Bulk import of model records
3305:      *
3306:      * The actual meat of the import process happens here, this is called recursively via
3307:      * AJAX to import sets of records. This is a refactored and generalized version of the
3308:      * old Contacts importRecords.
3309:      */
3310:     public function actionImportModelRecords() {
3311:         if (isset($_POST['count']) && file_exists($path = $this->safePath('data.csv')) &&
3312:                 isset($_POST['model'])) {
3313: 
3314:             ini_set('auto_detect_line_endings', 1); // Account for Mac based CSVs if possible
3315:             $importedIds = array();
3316:             $modelName = ucfirst($_POST['model']);
3317:             $count = $_POST['count']; // Number of records to import
3318:             $metaData = $_SESSION['metaData'];
3319:             $importMap = $_SESSION['importMap'];
3320:             $fp = fopen($path, 'r+');
3321:             fseek($fp, $_SESSION['offset']); // Seek to the right file offset
3322:             $mappedId = false; // Whether the user has mapped the ID field
3323: 
3324:             // validate meta data
3325:             if (!ArrayUtil::setEquals (array_keys ($importMap), $metaData)) {
3326:                 throw new CHttpException (400, Yii::t('app', 'Bad import map'));
3327:             }
3328: 
3329:             
3330: 
3331:             for ($i = 0; $i < $count; $i++) {
3332:                 // Loop through and start importing
3333:                 $csvLine = fgetcsv($fp, 0, $this->importDelimeter, $this->importEnclosure);
3334:                 if ($csvLine !== false && !is_null($csvLine)) {
3335:                     if ($csvLine === array(null)) {
3336:                         // Skip empty lines
3337:                         continue;
3338:                     }
3339:                     if (count($csvLine) > count($metaData))
3340:                         $csvLine = array_slice($csvLine, 0, count($metaData));
3341:                     else if (count($csvLine) < count($metaData))
3342:                         $csvLine = array_pad($csvLine, count($metaData), null);
3343:                     unset($_POST);
3344:                     if ($modelName === 'Actions')
3345:                         $this->setCurrentActionText();
3346: 
3347:                     // Nix all invalid multibyte sequences to avoid errors
3348:                     $csvLine = array_map('Formatter::mbSanitize', $csvLine);
3349:                     if (!empty($metaData) && !empty($csvLine))
3350:                         $importAttributes = array_combine($metaData, $csvLine);
3351:                     else
3352:                         continue;
3353: 
3354:                     
3355:                         $model = new $modelName;
3356:                     
3357: 
3358:                     foreach ($metaData as $attribute) {
3359:                         if ($importMap[$attribute] === 'id')
3360:                             $mappedId = true;
3361: 
3362:                         $isActionText = ($modelName === 'Actions' &&
3363:                             $importMap[$attribute] === 'actionDescription');
3364:                         if ($importMap[$attribute] === 'applyTags') {
3365:                             $this->importTags ($modelName, $importAttributes[$attribute]);
3366:                             continue;
3367:                         }
3368:                         if (isset($importMap[$attribute]) &&
3369:                                 ($model->hasAttribute($importMap[$attribute]) || $isActionText)) {
3370:                             $model = $this->importRecordAttribute ($modelName, $model, $importMap[$attribute], $importAttributes[$attribute]);
3371:                             $_POST[$importMap[$attribute]] = $model->$importMap[$attribute];
3372:                         }
3373:                     }
3374:                     $this->fixupImportedAttributes ($modelName, $model);
3375: 
3376:                     if (!$model->hasErrors() && $model->validate())
3377:                         $importedIds = $this->saveImportedModel ($model, $modelName, $importedIds);
3378:                     else
3379:                         $this->markFailedRecord ($modelName, $model, $csvLine, $metaData);
3380:                 } else {
3381:                     $this->finishImportBatch ($modelName, $mappedId, true);
3382:                     return;
3383:                 }
3384:             }
3385:             // Change the offset to wherever we got to and continue.
3386:             $_SESSION['offset'] = ftell($fp);
3387:             $this->finishImportBatch ($modelName, $mappedId);
3388:         }
3389:     }
3390: 
3391:     /**
3392:      * Import a zip of a module.
3393:      *
3394:      * This method will allow the admin to import a zip file of an exported X2
3395:      * module.
3396:      */
3397:     public function actionImportModule() {
3398: 
3399:         if (isset($_FILES['data'])) {
3400: 
3401:             $module = Yii::app()->file->set('data');
3402:             if (!$module->exists) {
3403:                 Yii::app()->user->setFlash('error', Yii::t('admin', 'There was an error uploading the module.'));
3404:                 $this->redirect('importModule');
3405:             }
3406: 
3407:             $moduleName = $module->filename;
3408:             
3409:             if (X2Model::model('Modules')->findByAttributes(array('name' => $moduleName))) {
3410:                 Yii::app()->user->setFlash('error', Yii::t('admin', 'Unable to upload module. A module with this name already exists.'));
3411:                 $this->redirect('importModule');
3412:             }
3413:             if ($module->extension !== 'zip') {
3414:                 Yii::app()->user->setFlash('error', Yii::t('admin', 'There was an error uploading the module. Please select a valid zip archive.'));
3415:                 $this->redirect('importModule');
3416:             }
3417: 
3418:             $filename = $this->asa('ImportExportBehavior')->safePath($moduleName . ".zip");
3419:             if ($module->copy($filename) === false || !file_exists($filename)) {
3420:                 Yii::app()->user->setFlash('error', Yii::t('admin', "There was an error saving the module."));
3421:                 $this->redirect('importModule');
3422:             }
3423:             $zip = Yii::app()->zip;
3424:             if ($zip->extractZip($filename, 'protected/modules/') === false) {
3425:                 Yii::app()->user->setFlash('error', Yii::t('admin', "There was an error unzipping the module. Please ensure the zip archive is not corrupt."));
3426:                 $this->redirect('importModule');
3427:             }
3428: 
3429:             if ($this->loadModuleData($moduleName)) {
3430:                 unlink($filename);
3431: 
3432:                 $this->createDefaultModulePermissions(ucfirst($moduleName));
3433:                 $this->fixupImportedModuleDropdowns(array($moduleName));
3434: 
3435:                 $this->redirect(array($moduleName . '/index'));
3436:             } else {
3437:                 Yii::app()->user->setFlash(
3438:                     'error',
3439:                     Yii::t('admin', 'Failed to load module data. Please ensure that the archive '.
3440:                         'is in the expected format.')
3441:                 );
3442:                 $this->redirect('importModule');
3443:             }
3444:         }
3445:         $this->render('importModule');
3446:     }
3447: 
3448:     /**
3449:      * Private helper function to load module SQL
3450:      * @param string $moduleName Name of the module to install SQL from
3451:      * @return boolean Whether the module data was successfully loaded
3452:      */
3453:     private function loadModuleData($moduleName) {
3454:         $regPath = implode(DIRECTORY_SEPARATOR, array(
3455:             'protected', 'modules', $moduleName, 'register.php'
3456:         ));
3457:         $regFile = realpath($regPath);
3458:         if ($regFile) {
3459:             $install = require_once($regFile);
3460:             foreach ($install['install'] as $sql) {
3461:                 $sqlComm = $sql;
3462:                 if (is_string($sql)) {
3463:                     if (file_exists($sql)) {
3464:                         $sqlComm = explode('/*&*/', file_get_contents($sql));
3465:                     }
3466:                 }
3467:                 foreach ($sqlComm as $sqlLine) {
3468:                     if (!empty($sqlLine)) {
3469:                         $command = Yii::app()->db->createCommand($sqlLine);
3470:                         $command->execute();
3471:                     }
3472:                 }
3473:             }
3474:             return true;
3475:         } else {
3476:             return false;
3477:         }
3478:     }
3479: 
3480:     /**
3481:      * @deprecated
3482:      * DO NOT USE
3483:      *
3484:      * Testing method used for a prototype system of managing modules in a more
3485:      * modular fashion.  This is NOT ready for use and should not be accessed.
3486:      * This is intended to actually allow turning on/off of modules and installation.
3487:      * This has been mostly superceded by the import/export feature but a use may
3488:      * yet be found for it.
3489:      */
3490:     public function actionRegisterModules() {
3491: 
3492:         $modules = scandir('protected/modules');
3493:         $modules = array_combine($modules, $modules);
3494:         $arr = array();
3495:         foreach ($modules as $module) {
3496:             if (file_exists("protected/modules/$module/register.php") && is_null(Modules::model()->findByAttributes(array('name' => $module)))) {
3497:                 $arr[] = ($module);
3498:             }
3499:         }
3500:         $registeredModules = Modules::model()->findAll();
3501: 
3502:         $this->render('registerModules', array(
3503:             'modules' => $arr,
3504:             'registeredModules' => $registeredModules,
3505:         ));
3506:     }
3507: 
3508:     /**
3509:      * @deprecated
3510:      * DO NOT USE
3511:      *
3512:      * Like {@link actionRegisterModules} this method is not yet ready for use.
3513:      * Please refrain from attempting to use this module or it will likely create
3514:      * issues in your installation.
3515:      *
3516:      * @param string $module The name of the module being toggled.
3517:      */
3518:     public function actionToggleModule($module) {
3519: 
3520:         $config = include("protected/modules/$module/register.php");
3521:         $exists = Modules::model()->findByAttributes(array('name' => $module));
3522:         if (!isset($exists)) {
3523:             $moduleRecord = new Modules;
3524:             $moduleRecord->editable = $config['editable'] ? 1 : 0;
3525:             $moduleRecord->searchable = $config['searchable'] ? 1 : 0;
3526:             $moduleRecord->adminOnly = $config['adminOnly'] ? 1 : 0;
3527:             $moduleRecord->custom = $config['custom'] ? 1 : 0;
3528:             $moduleRecord->toggleable = $config['toggleable'] ? 1 : 0;
3529:             $moduleRecord->name = $module;
3530:             $moduleRecord->title = $config['name'];
3531:             $moduleRecord->visible = 1;
3532:             $moduleRecord->menuPosition = Modules::model()->count();
3533: 
3534:             if ($moduleRecord->save()) {
3535:                 $install = $config['install'];
3536:             }
3537:         } else {
3538:             $exists->visible = $exists->visible ? 0 : 1;
3539: 
3540:             if ($exists->save()) {
3541:                 if ($exists->toggleable) {
3542:                     $uninstall = $config['uninstall'];
3543:                 } else {
3544:                     
3545:                 }
3546:             }
3547:         }
3548:         $this->redirect('registerModules');
3549:     }
3550: 
3551:     /**
3552:      * X2Studio Form Editor
3553:      *
3554:      * This method allows the admin to create and edit the form layouts for
3555:      * all editable modules within the software.
3556:      */
3557:     public function actionEditor() {
3558: 
3559:         $layoutModel = null;
3560:         $defaultView = false;
3561:         $defaultForm = false;
3562: 
3563:         if (isset($_GET['id']) && !empty($_GET['id'])) {
3564: 
3565:             $id = $_GET['id'];
3566:             $layoutModel = FormLayout::model()->findByPk($id);
3567: 
3568:             if (!isset($layoutModel))
3569:                 $this->redirect(array('editor'));
3570: 
3571:             $modelName = $layoutModel->model;
3572: 
3573:             if (isset($_POST['layout'])) {
3574:                 $layoutModel->layout = urldecode($_POST['layout']);
3575:                 $layoutModel->defaultView = isset($_POST['defaultView']) && $_POST['defaultView'] == 1;
3576:                 $layoutModel->defaultForm = isset($_POST['defaultForm']) && $_POST['defaultForm'] == 1;
3577:                 $layoutModel->scenario = isset($_POST['scenario']) ? $_POST['scenario'] : 'Default';
3578: 
3579:                 // if this is the default view, unset defaultView for all other forms
3580:                 if ($layoutModel->defaultView)
3581:                     FormLayout::clearDefaultFormLayouts('view', $modelName, $layoutModel->scenario);
3582:                 // if this is the default form, unset defaultForm for all other forms
3583:                 if ($layoutModel->defaultForm)
3584:                     FormLayout::clearDefaultFormLayouts('form', $modelName, $layoutModel->scenario);
3585: 
3586:                 $layoutModel->save();
3587:                 $this->redirect(array('editor', 'id' => $id));
3588:             }
3589:         }else {
3590:             $modelName = isset($_GET['model']) ? $_GET['model'] : '';
3591:             if (!empty($modelName)) {
3592:                 try {
3593:                     $model = X2Model::model($modelName);
3594:                 } catch (Exception $e) {
3595:                     throw new CHttpException(400, 'The model you have requested does not exist. Please do not repeat this request.');
3596:                 }
3597:             }
3598:             $id = '';
3599:         }
3600: 
3601:         $modules = Modules::model()->findAllByAttributes(array('editable' => 1));
3602: 
3603:         $modelList = array('' => '---');
3604:         foreach ($modules as $module) {
3605:             if ($module->name == 'marketing')
3606:                 $modelList['Campaign'] = Yii::t('marketing', 'Campaign');
3607:             elseif ($module->name == 'opportunities')
3608:                 $modelList['Opportunity'] = Yii::t('opportunities', 'Opportunity');
3609:             elseif ($module->name == 'products')
3610:                 $modelList['Product'] = Yii::t('products', 'Product');
3611:             elseif ($module->name == 'quotes')
3612:                 $modelList['Quote'] = Yii::t('quotes', 'Quote');
3613:             else
3614:                 $modelList[ucfirst($module->name)] = Yii::t('app', $module->title);
3615:         }
3616: 
3617:         $versionList = array('' => '---');
3618:         if (!empty($modelName)) {
3619:             $layouts = FormLayout::model()->findAllByAttributes(array('model' => $modelName));
3620: 
3621:             foreach ($layouts as &$layout)
3622:                 $versionList[$layout->id] = $layout->version . (($layout->defaultView || $layout->defaultForm) ? ' (' . Yii::t('admin', 'Default') . ')' : '');
3623:             unset($layout);
3624:         }
3625: 
3626:         $this->render('editor', array(
3627:             'modelName' => $modelName,
3628:             'id' => $id,
3629:             'layoutModel' => $layoutModel,
3630:             'modelList' => $modelList,
3631:             'versionList' => $versionList,
3632:             'defaultView' => isset($layoutModel->defaultView) ? $layoutModel->defaultView : false,
3633:             'defaultForm' => isset($layoutModel->defaultForm) ? $layoutModel->defaultForm : false,
3634:         ));
3635:     }
3636: 
3637:     /**
3638:      * Create form Layout
3639:      *
3640:      * This method is called via AJAX from within {@link actionEditor} to create
3641:      * new form layouts for use with the modules.
3642:      */
3643:     public function actionCreateFormLayout() {
3644:         if (isset($_GET['newLayout'], $_GET['model'], $_GET['layoutName'])) {
3645:             // $currentLayouts = FormLayout::model()->findAllByAttributes(array('model'=>$_GET['model']));
3646: 
3647:             $newLayout = new FormLayout;
3648: 
3649:             if (isset($_POST['layout']))
3650:                 $newLayout->layout = urldecode($_POST['layout']);
3651: 
3652:             $newLayout->version = $_GET['layoutName'];
3653:             $newLayout->model = $_GET['model'];
3654:             $newLayout->createDate = time();
3655:             $newLayout->lastUpdated = time();
3656:             $newLayout->defaultView = false;
3657:             $newLayout->defaultForm = false;
3658:             $newLayout->save();
3659:             $this->redirect(array('editor', 'id' => $newLayout->id));
3660:         }
3661:     }
3662: 
3663:     /**
3664:      * Delete a form layout.
3665:      *
3666:      * @param int $id The ID of the layout to be deleted.
3667:      */
3668:     public function actionDeleteFormLayout($id) {
3669: 
3670:         $layout = FormLayout::model()->findByPk($id);
3671:         if (isset($layout)) {
3672:             $modelName = $layout->model;
3673:             $defaultView = $layout->defaultView;
3674:             $defaultForm = $layout->defaultForm;
3675:             $layout->delete();
3676: 
3677:             // if we just deleted the default, find the next layout and make it the default
3678:             if ($defaultView) {
3679:                 $newDefaultView = FormLayout::model()->findByAttributes(array('model' => $modelName));
3680:                 if (isset($newDefaultView)) {
3681:                     $newDefaultView->defaultView = true;
3682:                     $newDefaultView->save();
3683:                 }
3684:             }
3685:             if ($defaultForm) {
3686:                 $newDefaultForm = FormLayout::model()->findByAttributes(array('model' => $modelName));
3687:                 if (isset($newDefaultForm)) {
3688:                     $newDefaultForm->defaultForm = true;
3689:                     $newDefaultForm->save();
3690:                 }
3691:             }
3692:             $this->redirect(array('editor', 'model' => $modelName));
3693:         } else
3694:             $this->redirect('editor');
3695:     }
3696: 
3697:     /**
3698:      * Landing page for admin created dropdowns
3699:      *
3700:      * This method allows the admin to access the functions related to creating
3701:      * and editing admin created dropdowns in the app.
3702:      */
3703:     public function actionManageDropDowns() {
3704: 
3705:         $dataProvider = new CActiveDataProvider('Dropdowns');
3706:         $model = new Dropdowns;
3707: 
3708:         $dropdowns = $dataProvider->getData();
3709:         foreach ($dropdowns as $dropdown) {
3710:             $temp = json_decode($dropdown->options, true);
3711:             if (is_array($temp)) {
3712:                 $str = implode(", ", $temp);
3713:             } else {
3714:                 $str = $dropdown->options;
3715:             }
3716:             $dropdown->options = $str;
3717:         }
3718:         $dataProvider->setData($dropdowns);
3719: 
3720:         $this->render('manageDropDowns', array(
3721:             'dataProvider' => $dataProvider,
3722:             'model' => $model,
3723:             'dropdowns' => Dropdowns::model()->findAll(),
3724:         ));
3725:     }
3726: 
3727:     /**
3728:      * Create a custom dropdown
3729:      *
3730:      * This method allows the admin to create a custom dropdown to be used with
3731:      * a module in conjunction with the form editor.
3732:      */
3733:     public function actionDropDownEditor() {
3734:         $model = new Dropdowns;
3735: 
3736:         if (isset($_POST['Dropdowns'])) {
3737:             $model->attributes = $_POST['Dropdowns'];
3738:             $temp = array();
3739:             if (isset($model->options)) {
3740:                 foreach ($model->options as $option) {
3741:                     if ($option != "") {
3742:                         $temp[$option] = $option;
3743:                     }
3744:                 }
3745:             }
3746:             if (count($temp) > 0) {
3747:                 $model->options = json_encode($temp);
3748:                 if ($model->save()) {
3749:                     
3750:                 }
3751:             }
3752:             $this->redirect(
3753:                     'manageDropDowns'
3754:             );
3755:         }
3756:     }
3757: 
3758:     /**
3759:      * Delete a custom dropdown
3760:      */
3761:     public function actionDeleteDropdown() {
3762:         $dropdowns = Dropdowns::model()->findAll('id>=1000');
3763: 
3764:         if (isset($_POST['dropdown'])) {
3765:             if ($_POST['dropdown'] != Actions::COLORS_DROPDOWN_ID) {
3766:                 $model = Dropdowns::model()->findByPk($_POST['dropdown']);
3767:                 $model->delete();
3768:                 $this->redirect('manageDropDowns');
3769:             }
3770:         }
3771: 
3772:         $this->redirect ('manageDropdowns');
3773:     }
3774: 
3775:     /**
3776:      * Edit a previously created dropdown
3777:      */
3778:     public function actionEditDropdown() {
3779:         $model = new Dropdowns;
3780: 
3781:         // TODO: validate dropdown select client-side
3782:         if (isset($_POST['Dropdowns']['id']) && ctype_digit($_POST['Dropdowns']['id'])) {
3783:             $model = Dropdowns::model()->findByPk(
3784:                     $_POST['Dropdowns']['id']);
3785:             if (!isset($model)) {
3786:                 throw new CHttpException(404, Yii::t('app', 'Dropdown could not be found'));
3787:             }
3788:             if ($model->id == Actions::COLORS_DROPDOWN_ID) {
3789:                 if (AuxLib::issetIsArray($_POST['Dropdowns']['values']) &&
3790:                         AuxLib::issetIsArray($_POST['Dropdowns']['labels']) &&
3791:                         count($_POST['Dropdowns']['values']) ===
3792:                         count($_POST['Dropdowns']['labels'])) {
3793: 
3794:                     if (AuxLib::issetIsArray($_POST['Admin']) &&
3795:                             isset($_POST['Admin']['enableColorDropdownLegend'])) {
3796: 
3797:                         Yii::app()->settings->enableColorDropdownLegend = $_POST['Admin']['enableColorDropdownLegend'];
3798:                         Yii::app()->settings->save();
3799:                     }
3800: 
3801:                     $options = array_combine(
3802:                             $_POST['Dropdowns']['values'], $_POST['Dropdowns']['labels']);
3803:                     $temp = array();
3804:                     foreach ($options as $value => $label) {
3805:                         if ($value != "")
3806:                             $temp[$value] = $label;
3807:                     }
3808:                     $model->options = json_encode($temp);
3809:                     $model->save();
3810:                 }
3811:             } else {
3812:                 $model->attributes = $_POST['Dropdowns'];
3813:                 $temp = array();
3814:                 if (is_array($model->options) && count($model->options) > 0) {
3815:                     foreach ($model->options as $option) {
3816:                         if ($option != "")
3817:                             $temp[$option] = $option;
3818:                     }
3819:                     $model->options = json_encode($temp);
3820:                     if ($model->save()) {
3821:                         
3822:                     }
3823:                 }
3824:             }
3825:         }
3826:         $this->redirect(
3827:                 'manageDropDowns'
3828:         );
3829:     }
3830: 
3831:     /**
3832:      * Print out a dropdown's data
3833:      *
3834:      * This method is called via AJAX by {@link actionEditDropdown} to get the
3835:      * options of the dropdown for the edit dropdown page.
3836:      */
3837:     public function actionGetDropdown() {
3838:         if (isset($_POST['Dropdowns']['id'])) {
3839:             $id = $_POST['Dropdowns']['id'];
3840:             $model = Dropdowns::model()->findByPk($id);
3841:             if ($model === null) {
3842:                 return;
3843:             }
3844: 
3845:             $options = json_decode($model->options);
3846:             if ($id == Actions::COLORS_DROPDOWN_ID) {
3847:                 $this->renderPartial(
3848:                         'application.modules.actions.views.actions._colorDropdownForm', array(
3849:                     'model' => $model,
3850:                     'options' => $options,
3851:                         ), false, true);
3852:             } else {
3853:                 $this->renderPartial(
3854:                         'application.components.views._dropdownForm', array(
3855:                     'model' => $model,
3856:                     'options' => $options,
3857:                         ), false, true);
3858:             }
3859:         }
3860:     }
3861: 
3862:     /**
3863:      * Echos a list of custom dropdowns
3864:      *
3865:      * This method is called via AJAX on the field editor to get a list of dropdowns
3866:      * or modules to be used for modifying the type of field.
3867:      */
3868:     public function actionGetFieldType() {
3869:         if (isset($_POST['Fields']['type'])) {
3870:             $field = new Fields;
3871:             $field->attributes = $_POST['Fields'];
3872:             $type = $_POST['Fields']['type'];
3873:             $model = new AmorphousModel();
3874:             $model->addField($field, 'customized_field');
3875: 
3876:             $this->renderPartial('fieldDefault', array(
3877:                 'field' => $field,
3878:                 'dummyModel' => $model,
3879:                 'type' => $type,
3880:                 'echoScripts' => true
3881:             ));
3882:         }
3883:     }
3884: 
3885:     /**
3886:      * Export all data
3887:      *
3888:      * This method is used to export all of the data from the software as a CSV
3889:      */
3890:     public function actionExport() {
3891:         $modelList = array(
3892:             'Admin' => array('name' => Yii::t('admin', 'Admin Settings'), 'count' => 1),
3893:         );
3894:         $modules = Modules::model()->findAll();
3895:         foreach ($modules as $module) {
3896:             $name = ucfirst($module->name);
3897:             if ($name != 'Document') {
3898:                 $controllerName = $name . 'Controller';
3899:                 if (file_exists('protected/modules/' . $module->name . '/controllers/' . $controllerName . '.php')) {
3900:                     Yii::import("application.modules.$module->name.controllers.$controllerName");
3901:                     $controller = new $controllerName($controllerName);
3902:                     $model = $controller->modelClass;
3903:                     if (class_exists($model)) {
3904:                         $recordCount = X2Model::model($model)->count();
3905:                         if ($recordCount > 0) { // Only display modules we actually have data for...
3906:                             $modelList[$model] = array('name' => Yii::t('app', $module->title), 'count' => $recordCount);
3907:                         }
3908:                     }
3909:                 }
3910:             }
3911:         }
3912:         $extraModels = array('Fields', 'Dropdowns', 'FormLayout');
3913:         foreach ($extraModels as $model) {
3914:             if (class_exists($model)) {
3915:                 $fieldCount = X2Model::model($model)->count();
3916:                 if ($fieldCount > 0)
3917:                     $modelList[$model] = array('name' => Yii::t('app', $model), 'count' => $fieldCount);
3918:             }
3919:         }
3920: 
3921:         $this->render('export', array(
3922:             'modelList' => $modelList,
3923:         ));
3924:     }
3925: 
3926:     ///////////////////
3927:     // GLOBAL EXPORT //
3928:     ///////////////////
3929: 
3930:     /**
3931:      * Helper function to generate the necessary CSV via ajax and insert version data.
3932:      */
3933:     public function actionPrepareExport() {
3934:         // Retrieve specified export delimeter, enclosure, and format options
3935:         $_SESSION['importDelimeter'] = (isset($_GET['delimeter']) ? $_GET['delimeter'] : ',');
3936:         $_SESSION['importEnclosure'] = (isset($_GET['enclosure']) ? $_GET['enclosure'] : '"');
3937:         $_SESSION['exportFormat'] = $this->readExportFormatOptions($_GET);
3938: 
3939:         $fp = fopen($this->safePath(), 'w+');
3940:         fputcsv($fp, array('v' . Yii::app()->params->version), $this->importDelimeter, $this->importEnclosure);
3941:         fclose($fp);
3942:     }
3943: 
3944:     /**
3945:      * An AJAX called method to export module data.
3946:      *
3947:      * This method actually prepares all the data via recursive AJAX requests
3948:      * until all data has been exported. This exports each module into the CSV
3949:      * by class, using pagination to cut down on request time.
3950:      *
3951:      * @param string $model The name of the current model being exported
3952:      * @param int $page The page of data which the data provider's paginator is on
3953:      */
3954:     public function actionGlobalExport($model, $page) {
3955:         if (class_exists($model)) {
3956:             ini_set('memory_limit', -1);
3957:             $file = $this->safePath();
3958:             $fp = fopen($file, 'a+');
3959:             $tempModel = X2Model::model($model);
3960:             $meta = array_keys($tempModel->attributes);
3961:             $meta[] = $model;
3962:             if ($page == 0) // If we're on the first page for this model, need to add metadata.
3963:                 fputcsv ($fp, $meta, $this->importDelimeter, $this->importEnclosure);
3964:             $dp = new CActiveDataProvider($model, array(
3965:                 'pagination' => array(
3966:                     'pageSize' => 100,
3967:                 ),
3968:             ));
3969:             $pg = $dp->getPagination();
3970:             $pg->setCurrentPage($page); // These two lines will set the data provider
3971:             $dp->setPagination($pg); // paginator to the requested page of data
3972:             $records = $dp->getData();
3973:             $pageCount = $dp->getPagination()->getPageCount(); // Total number of pages
3974: 
3975:             foreach ($records as $record) {
3976:                 // Re-pack all unpacked attributes for writing to a file, so that
3977:                 // they can be interpolated as strings:
3978:                 foreach ($record->behaviors() as $name => $config) {
3979:                     $behavior = $record->asa($name);
3980:                     if ($behavior instanceof TransformedFieldStorageBehavior) {
3981:                         $behavior->packAll();
3982:                         $record->disableBehavior($name);
3983:                     }
3984:                 }
3985:                 $tempAttributes = $tempModel->attributes;
3986:                 $tempAttributes = array_merge($tempAttributes, $record->attributes);
3987:                 if ($model == 'Profile') {
3988:                     $tempAttributes['theme'] = json_encode($record->theme);
3989:                 }
3990:                 $tempAttributes[] = $model;
3991:                 // Export the data to CSV
3992:                 fputcsv($fp, $tempAttributes, $this->importDelimeter, $this->importEnclosure);
3993:             }
3994: 
3995:             unset($tempModel, $dp);
3996: 
3997:             fclose($fp);
3998:             if ($page + 1 < $pageCount) {
3999:                 echo $page + 1; // If there are still more pages to go, echo the next page number
4000:             }
4001:         }
4002:     }
4003: 
4004:     /**
4005:      * To be called via AJAX to finalize a global export and prepare the export deliverable
4006:      */
4007:     public function actionFinishGlobalExport() {
4008:         $success = $this->prepareExportDeliverable ($this->safePath(), $_SESSION['exportFormat']);
4009:         if ($_SESSION['exportFormat']['exportDestination'] === 'download') {
4010:             $_SESSION['modelExportFile'] = $this->safePath();
4011:             if ($_SESSION['exportFormat']['compressOutput']) {
4012:                 $_SESSION['modelExportFile'] = $this->adjustExportPath (
4013:                     $this->safePath(),
4014:                     $_SESSION['exportFormat']
4015:                 );
4016:             }
4017:         } else {
4018:             $_SESSION['modelExportFile'] = '';
4019:         }
4020:         echo basename ($_SESSION['modelExportFile']);
4021:         unset ($_SESSION['exportFormat'], $_SESSION['modelExportFile']);
4022:     }
4023: 
4024: //  $file = Yii::app()->file->set($this->safePath($file));
4025:     /**
4026:      * Helper function called in a lot of places to download a file
4027:      * @param string $file Filepath of the requested file
4028:      */
4029:     public function actionDownloadData($file) {
4030:         $this->sendFile($file);
4031:     }
4032: 
4033:     /**
4034:      * An AJAX called function used to rollback a data import.
4035:      *
4036:      * This function is called several times with different parameters as a part
4037:      * of the rollback process and runs a variety of SQL queries to remove data
4038:      * created as part of the import process.
4039:      * @param string $model The name of the model Class
4040:      * @param string $stage The stage to be run for this step
4041:      * @param int $importId The ID of the import being rolled back
4042:      */
4043:     public function actionRollbackStage($model, $stage, $importId) {
4044:         $stages = array(
4045:             // Delete all tag data
4046:             "tags" => "DELETE a FROM x2_tags a
4047:                 INNER JOIN
4048:                 x2_imports b ON b.modelId=a.itemId AND b.modelType=a.type
4049:                 WHERE b.modelType='$model' AND b.importId='$importId'",
4050:             // Delete all relationship data
4051:             "relationships" => "DELETE a FROM x2_relationships a
4052:                 INNER JOIN
4053:                 x2_imports b ON b.modelId=a.firstId AND b.modelType=a.firstType
4054:                 WHERE b.modelType='$model' AND b.importId='$importId'",
4055:             // Delete any associated actions
4056:             "actions" => "DELETE a FROM x2_actions a
4057:                 INNER JOIN
4058:                 x2_imports b ON b.modelId=a.associationId AND b.modelType=a.associationType
4059:                 WHERE b.modelType='$model' AND b.importId='$importId'",
4060:             // Delete the records themselves
4061:             "records" => "DELETE a FROM " . X2Model::model($model)->tableName() . " a
4062:                 INNER JOIN
4063:                 x2_imports b ON b.modelId=a.id
4064:                 WHERE b.modelType='$model' AND b.importId='$importId'",
4065:             // Delete the log of the records being imported
4066:             "import" => "DELETE FROM x2_imports WHERE modelType='$model' AND importId='$importId'",
4067:         );
4068:         $sqlQuery = $stages[$stage];
4069:         $command = Yii::app()->db->createCommand($sqlQuery);
4070:         $result = $command->execute();
4071:         echo $result;
4072:     }
4073: 
4074:     /**
4075:      * An administrative view to rollback any data imports which have been conducted.
4076:      */
4077:     public function actionRollbackImport() {
4078:         // If an import ID is passed, load specific information about this import
4079:         if (isset($_GET['importId']) && ctype_digit($_GET['importId'])) {
4080:             $importId = $_GET['importId'];
4081:             $types = Yii::app()->db->createCommand()
4082:                     ->select('modelType')
4083:                     ->from('x2_imports')
4084:                     ->group('modelType')
4085:                     ->where('importId=:importId', array(':importId' => $importId))
4086:                     ->queryAll();
4087:             $count = Yii::app()->db->createCommand()
4088:                     ->select('COUNT(*)')
4089:                     ->from('x2_imports')
4090:                     ->group('importId')
4091:                     ->where('importId=:importId', array(':importId' => $importId))
4092:                     ->queryRow();
4093:             $count = $count['COUNT(*)'];
4094:             $typeArray = array();
4095:             foreach ($types as $tempArr) {
4096:                 $typeArray[] = $tempArr['modelType'];
4097:             }
4098:             $this->render('rollbackImport', array(
4099:                 'typeArray' => $typeArray,
4100:                 'dataProvider' => null,
4101:                 'count' => $count,
4102:                 'importId' => $importId,
4103:             ));
4104:         } else {
4105:             // Otherwise, load a list of imports to choose from
4106:             $data = array();
4107:             $imports = Yii::app()->db->createCommand()
4108:                     ->select('importId')
4109:                     ->from('x2_imports')
4110:                     ->group('importId')
4111:                     ->queryAll();
4112:             foreach ($imports as $key => $array) {
4113:                 $data[$key]['id'] = $key;
4114:                 $data[$key]['importId'] = $array['importId'];
4115:                 $count = Yii::app()->db->createCommand()
4116:                         ->select('COUNT(*)')
4117:                         ->from('x2_imports')
4118:                         ->group('importId')
4119:                         ->where('importId=:importId', array(':importId' => $array['importId']))
4120:                         ->queryRow();
4121:                 $data[$key]['type'] = Yii::app()->db->createCommand()
4122:                         ->select('modelType')
4123:                         ->from('x2_imports')
4124:                         ->where('importId=:importId', array(':importId' => $array['importId']))
4125:                         ->queryScalar();
4126:                 $data[$key]['records'] = $count['COUNT(*)'];
4127:                 $timestamp = Yii::app()->db->createCommand()
4128:                         ->select('timestamp')
4129:                         ->from('x2_imports')
4130:                         ->group('importId')
4131:                         ->order('timestamp ASC')
4132:                         ->where('importId=:importId', array(':importId' => $array['importId']))
4133:                         ->queryRow();
4134:                 $data[$key]['timestamp'] = $timestamp['timestamp'];
4135:                 $data[$key]['link'] = "";
4136:             }
4137:             $dataProvider = new CArrayDataProvider($data);
4138:             $this->render('rollbackImport', array(
4139:                 'typeArray' => array(),
4140:                 'dataProvider' => $dataProvider,
4141:             ));
4142:         }
4143:     }
4144: 
4145:     /**
4146:      * Import data from a CSV
4147:      *
4148:      * This method allows for the import of data by the admin into the software.
4149:      * This import expects machine readable data (i.e. data which would be directly
4150:      * inserted into the database like unix timestamps) and the final column of
4151:      * each row should be the type of record being imported (e.g. Contacts, Actions, etc.)
4152:      * This particular function merely renders the upload page.
4153:      */
4154:     public function actionImport() {
4155:         $formModel = new GlobalImportFormModel;
4156: 
4157:         if (isset($_POST['GlobalImportFormModel']) && isset($_FILES['GlobalImportFormModel'])) {
4158:             $formModel->setAttributes($_POST['GlobalImportFormModel']);
4159:             $formModel->data = CUploadedFile::getInstance($formModel, 'data');
4160: 
4161:             if ($formModel->validate()) {
4162:                 $_SESSION['overwrite'] = $formModel->overwrite;
4163:                 $_SESSION['counts'] = array();
4164:                 $_SESSION['overwriten'] = array();
4165:                 $_SESSION['overwriteFailure'] = array();
4166:                 $_SESSION['model'] = "";
4167:                 $_SESSION['failed'] = 0;
4168:                 $_SESSION['importDelimeter'] = $formModel->delimeter;
4169:                 $_SESSION['importEnclosure'] = $formModel->enclosure;
4170: 
4171:                 if ($formModel->data->saveAs($this->safePath())) {
4172:                     // If we have post data, render the import processing page
4173:                     $this->render('processImport', array(
4174:                         'overwrite' => $formModel->overwrite,
4175:                     ));
4176:                     Yii::app()->end();
4177:                 } else {
4178:                     $formModel->addError('data', Yii::t('admin', 'File could not be uploaded'));
4179:                 }
4180:             }
4181:         }
4182: 
4183:         $this->render('import', array(
4184:             'formModel' => $formModel
4185:         ));
4186:     }
4187: 
4188:     /**
4189:      * Helper function to prepare a lot of the necessary information for a data
4190:      * import. A large amount of this data is stored in the session so as to be
4191:      * preserved between the AJAX requests which will occur as a part of the import
4192:      * process.
4193:      */
4194:     public function actionPrepareImport() {
4195:         $fp = fopen($this->safePath(), 'r+');
4196:         // The first row should be just the version number of the data
4197:         $version = fgetcsv($fp, 0, $this->importDelimeter, $this->importEnclosure);
4198:         $version = $version[0];
4199:         $tempMeta = fgetcsv($fp, 0, $this->importDelimeter, $this->importEnclosure);
4200:         while ("" === end($tempMeta)) { // Clear all blank rows from the metadata
4201:             array_pop($tempMeta);
4202:         }
4203:         $model = array_pop($tempMeta); // The last column should be the model class
4204:         $_SESSION['metaData'] = $tempMeta; // Store the current metadata
4205:         $_SESSION['model'] = $model; // Store the current class
4206:         $_SESSION['lastFailed'] = "";
4207:         /*
4208:          * THIS IS ESSENTIAL. The ftell function reads the current position in the
4209:          * file so we know where to start from next time. All AJAX based imports
4210:          * will neeed to use this function.
4211:          */
4212:         $_SESSION['offset'] = ftell($fp);
4213:         fclose($fp);
4214:         $criteria = new CDbCriteria;
4215:         $criteria->order = "importId DESC";
4216:         $criteria->limit = 1;
4217:         $import = Imports::model()->find($criteria);
4218:         if (isset($import)) { // Set the ID of the current import to be 1 higher than the last one
4219:             $_SESSION['importId'] = $import->importId + 1;
4220:         } else {
4221:             $_SESSION['importId'] = 1;
4222:         }
4223:         $failedImport = fopen($this->safePath('failedImport.csv'), 'w+'); // Prepare a CSV for any failed records
4224:         fputcsv($failedImport, array(Yii::app()->params->version), $this->importDelimeter, $this->importEnclosure);
4225:         fclose($failedImport);
4226:         echo json_encode(array($version));
4227:     }
4228: 
4229:     /**
4230:      * Helper method to be called via ajax to prepare the model export by writing the
4231:      * CSV header and setting appropriate configuration in $_SESSION
4232:      */
4233:     public function actionPrepareModelExport() {
4234:         if (isset($_GET['model']) || isset($_POST['model'])) {
4235:             $model = (isset($_GET['model'])) ? $_GET['model'] : $_POST['model'];
4236:             $modelName = str_replace(' ', '', $model);
4237:         }
4238:         $_SESSION['includeTags'] = isset($_GET['includeTags']) && $_GET['includeTags'] === 'true';
4239:         $_SESSION['exportFormat'] = $this->readExportFormatOptions($_GET);
4240:         $filePath = $this->safePath($_SESSION['modelExportFile']);
4241:         $attributes = X2Model::model($modelName)->attributes;
4242: 
4243:         // Retrieve specified export delimeter and enclosure
4244:         $_SESSION['importDelimeter'] = (isset($_GET['delimeter']) ? $_GET['delimeter'] : ',');
4245:         $_SESSION['importEnclosure'] = (isset($_GET['enclosure']) ? $_GET['enclosure'] : '"');
4246: 
4247:         if ($modelName === 'Actions') {
4248:             // Make sure the ActionText is exported too
4249:             $attributes = array_merge($attributes, array('actionDescription' => null));
4250:         }
4251:         $meta = array_keys($attributes);
4252:         if ($_SESSION['includeTags'])
4253:             $meta[] = 'tags';
4254:         if (isset($_SESSION['exportModelListId'])) {
4255:             // Figure out gridview settings to export those columns
4256:             $gridviewSettings = json_decode(Yii::app()->params->profile->gridviewSettings, true);
4257:             if (isset($gridviewSettings['contacts_list' . $_SESSION['exportModelListId']])) {
4258:                 $tempMeta = array_keys($gridviewSettings['contacts_list' . $_SESSION['exportModelListId']]);
4259:                 $meta = array_intersect($tempMeta, $meta);
4260:             }
4261:         }
4262:         // Set up metadata
4263:         $_SESSION['modelExportMeta'] = $meta;
4264:         $fp = @fopen($filePath, 'w+');
4265:         if ($fp) {
4266:             fputcsv($fp, $meta, $this->importDelimeter, $this->importEnclosure);
4267:             fclose($fp);
4268:         } else {
4269:             $msg = Yii::t ('admin', 'Failed to open CSV file for writing. Please ensure the '.
4270:                 'protected/data directory is writable by the web server process.');
4271:             $this->respond ($msg, true);
4272:         }
4273:     }
4274: 
4275:     /**
4276:      * Helper method to be called via ajax to prepare for model import by setting
4277:      * necessary configuration in $_SESSION and verifying the chosen import map
4278:      */
4279:     public function actionPrepareModelImport() {
4280:         // Keys & attributes are our finalized import map
4281:         if (isset($_POST['attributes']) && isset($_POST['keys']) && isset($_POST['model'])) {
4282:             $model = $_POST['model'];
4283:             $keys = $_POST['keys'];
4284:             $attributes = $_POST['attributes'];
4285:             $preselectedMap = (isset($_POST['preselectedMap']) && $_POST['preselectedMap'] === 'true') ? true : false;
4286:             $_SESSION['tags'] = array();
4287:             // Grab any tags that need to be added to each record
4288:             if (isset($_POST['tags']) && !empty($_POST['tags'])) {
4289:                 $tags = explode(',', $_POST['tags']);
4290:                 foreach ($tags as $tag) {
4291:                     if (substr($tag, 0, 1) != "#")
4292:                         $tag = "#" . $tag;
4293:                     $_SESSION['tags'][] = $tag;
4294:                 }
4295:             }
4296:             // The override allows the user to specify fixed values for certain fields
4297:             $_SESSION['override'] = array();
4298:             if (isset($_POST['forcedAttributes']) && isset($_POST['forcedValues'])) {
4299:                 $override = array_combine($_POST['forcedAttributes'], $_POST['forcedValues']);
4300:                 $_SESSION['override'] = $override;
4301:             }
4302:             // Comments will log a comment on the record
4303:             $_SESSION['comment'] = "";
4304:             if (isset($_POST['comment']) && !empty($_POST['comment'])) {
4305:                 $_SESSION['comment'] = $_POST['comment'];
4306:             }
4307:             // Whether to use lead routing
4308:             $_SESSION['leadRouting'] = 0;
4309:             if (isset($_POST['routing']) && $_POST['routing'] == 1) {
4310:                 $_SESSION['leadRouting'] = 1;
4311:             }
4312:             // Whether to post the new records to the activity feed
4313:             $_SESSION['skipActivityFeed'] = 0;
4314:             if (isset($_POST['skipActivityFeed']) && $_POST['skipActivityFeed'] == 1) {
4315:                 $_SESSION['skipActivityFeed'] = 1;
4316:             }
4317:             // Whether to update existing records
4318:              
4319:             $_SESSION['createRecords'] = $_POST['createRecords'] == "checked" ? "1" : "0";
4320:             $_SESSION['linkMatchMap'] = empty($_POST['linkMatchMap']) ? array() : $_POST['linkMatchMap'];
4321:             $_SESSION['imported'] = 0;
4322:             $_SESSION['failed'] = 0;
4323:             $_SESSION['created'] = array();
4324:             if ($preselectedMap) {
4325:                 $keys = array_keys($_SESSION['importMap']);
4326:                 $attributes = array_values($_SESSION['importMap']);
4327:             }
4328:             
4329:             // Check for any non-unique fields used to match link type fields
4330:             $nonUniqueLinkMatches = array();
4331:             foreach ($_SESSION['linkMatchMap'] as $attr => $match) {
4332:                 $linkedModel = Yii::app()->db->createCommand()
4333:                     ->select ('linkType')
4334:                     ->from ('x2_fields')
4335:                     ->where ('fieldName = :field AND modelName = :model', array(
4336:                         ':field' => $attr,
4337:                         ':model' => $model,
4338:                     ))
4339:                     ->queryScalar();
4340:                 $matchField = Fields::model()->findByAttributes (array(
4341:                     'fieldName' => $match,
4342:                     'modelName' => $linkedModel,
4343:                     'uniqueConstraint' => 0,
4344:                 ));
4345:                 if ($matchField)
4346:                     $nonUniqueLinkMatches[$model.'.'.$attr] = $linkedModel.'.'.$matchField->fieldName;
4347:             }
4348:             $mappingResult = $this->verifyImportMap($model, $keys, $attributes);
4349:              
4350:             if (!empty($nonUniqueLinkMatches)) {
4351:                 // Warn the user that they are associating links on a non-unique field
4352:                 $mappingResult['nonUniqAssocMatchAttr'] = '';
4353:                 foreach ($nonUniqueLinkMatches as $attr => $mapping)
4354:                     $mappingResult['nonUniqAssocMatchAttr'] .= "* {$attr}: {$mapping}\n";
4355:             }
4356:             echo CJSON::encode ($mappingResult);
4357:             $cache = Yii::app()->cache;
4358:             if (isset($cache)) {
4359:                 $cache->flush();
4360:             }
4361:         }
4362:     }
4363: 
4364:     /**
4365:      * Allows for control of setting the externally visible URL for the CRM.
4366:      * This function is in the wrong place (in the middle of all the import functions)
4367:      * and should be cleaned up (or possibly refactored, see my notes on the Admin
4368:      * Controller refactor) but I'm only writing comments right now and trying
4369:      * not to make code modifications.
4370:      */
4371:     public function actionPublicInfo() {
4372:         $admin = &Yii::app()->settings;
4373:         if (isset($_POST['Admin'])) {
4374:             $admin->attributes = $_POST['Admin'];
4375:             $assetDomains = CJSON::decode ($admin->assetBaseUrls);
4376:             if (is_array($assetDomains)) {
4377:                 if (!empty($assetDomains))
4378:                     $admin->assetBaseUrls = array_unique ($assetDomains);
4379:                 else // Disable asset domains if none were specified
4380:                     $admin->enableAssetDomains = false;
4381:             }
4382:             if ($admin->save()) {
4383:                 $this->redirect('publicInfo');
4384:             }
4385:         }
4386: 
4387:         if ($admin->externalBaseUrl == '' && !$admin->hasErrors('externalBaseUrl'))
4388:             $admin->externalBaseUrl = Yii::app()->request->getHostInfo();
4389:         if ($admin->externalBaseUri == '' && !$admin->hasErrors('externalBaseUri'))
4390:             $admin->externalBaseUri = Yii::app()->baseUrl;
4391:         $this->render('publicInfo', array(
4392:             'model' => $admin,
4393:         ));
4394:     }
4395: 
4396:     /**
4397:      * Import a set of CSV data into the software.
4398:      *
4399:      * This function is called via AJAX and is the meat of the global import process.
4400:      * It takes the variable "count" as POST data to determine how many records
4401:      * it should import in this step, which is usually 50, but is arbitrary
4402:      * except for server load considerations. It reads data out of the "data.csv"
4403:      * file and imports it. See inline comments for details of what's going on.
4404:      *
4405:      * @return null A return statement to cease further execution, could probably be cleaned up & removed
4406:      */
4407:     public function actionGlobalImport() {
4408:         if (isset($_POST['count']) && file_exists($this->safePath())) {
4409:             $metaData = $_SESSION['metaData']; // Grab the most recent metadata
4410:             $modelType = $_SESSION['model']; // And model
4411:             $count = $_POST['count'];
4412:             $fp = fopen($this->safePath(), 'r+');
4413:             /*
4414:              * THIS IS ESSENTIAL. As with the above block noted as essential,
4415:              * this was KEY to figuring out how to do an AJAX based CSV read.
4416:              * The fseek function will move the file pointer to the specified offset,
4417:              * which we always store in the $_SESSION['offset'] variable.
4418:              */
4419:             fseek($fp, $_SESSION['offset']);
4420:             for ($i = 0; $i < $count; $i++) { // Loop up to the speficied count.
4421:                 // Grab the next row
4422:                 $csvLine = fgetcsv ($fp, 0, $this->importDelimeter, $this->importEnclosure);
4423:                 if ($csvLine !== false && !is_null($csvLine)) {
4424:                     while ("" === end($csvLine)) { // Remove blank space from the end
4425:                         array_pop($csvLine);
4426:                     }
4427:                     $newType = array_pop($csvLine); // Pull the last column to check the model type
4428:                     if ($newType != $modelType) {
4429:                         /*
4430:                          * If this is the first row of a new model type, the data
4431:                          * in the last column will be a different class name. In that
4432:                          * case, we assume this new row consists of the metadata
4433:                          * for this new model class and that the next set of records
4434:                          * will be of this model type. This information is stored
4435:                          * in the session in case a set of 50 records breaks
4436:                          * unevenly across model types (e.g. the system needs to import
4437:                          * more than 50 of a given record).
4438:                          */
4439:                         $_SESSION['model'] = $newType;
4440:                         $_SESSION['metaData'] = $csvLine;
4441:                         $modelType = $_SESSION['model'];
4442:                         $metaData = $_SESSION['metaData'];
4443:                     } else {
4444:                         $attributes = array_combine($metaData, $csvLine);
4445:                         if ($modelType == "Actions" && (isset($attributes['type']) &&
4446:                                 $attributes['type'] == 'workflow')) {
4447:                             // In the event that we're importing workflow, we need a special 
4448:                             // scenario.
4449:                             $model = new Actions('workflow');
4450:                         } else {
4451:                             $model = new $modelType;
4452:                         }
4453:                         /*
4454:                          * This loops through and sets the attributes manually.
4455:                          * Realistically, this could be refactored to use the
4456:                          * SetX2Fields function, but you'd need to be sure the
4457:                          * data wasn't double formatted (e.g. it's already a unix
4458:                          * timestamp, not a date string, and doesn't need to be
4459:                          * converted again) due to the fact that a user could supply
4460:                          * either human readable or machine readable data.
4461:                          */
4462:                         foreach ($attributes as $key => $value) {
4463:                             if ($model->hasAttribute($key) && isset($value)) {
4464:                                 if ($value == "")
4465:                                     $value = null;
4466:                                 $model->$key = $value;
4467:                             }
4468:                         }
4469:                         // Don't make a changelog record.
4470:                         $model->disableBehavior('changelog');
4471:                         // Don't manually set the timestamp fields
4472:                         $model->disableBehavior('X2TimestampBehavior');
4473:                         if ($model instanceof User || $model instanceof Profile) {
4474:                             if ($model->id == '1') {
4475:                                 /*
4476:                                  * If a model of type User with the ID of one is
4477:                                  * being imported skip so that we DO NOT
4478:                                  * OVERWRITE THE CURRENT ADMIN USER.
4479:                                  */
4480:                                 continue;
4481:                             }
4482:                             // Users & Profile normally require special validation, set a scenario for import
4483:                             $model->setScenario('import');
4484:                         }
4485:                         if ($_SESSION['overwrite'] == 1 &&
4486:                                 property_exists($model, 'subScenario')) {
4487: 
4488:                             $model->subScenario = 'importOverwrite';
4489:                         }
4490: 
4491:                         // If an ID was provided, check if there's already a model with that ID
4492:                         $lookup = X2Model::model($modelType)->findByPk($model->id);
4493:                         $lookupFlag = isset($lookup);
4494:                         /*
4495:                          * I'm not sure if "validate" will succeed anymore given the
4496:                          * change made to ID being a "unique" field in X2Model's rules
4497:                          * This should be investigated at some point.
4498:                          */
4499:                         if ($model->validate() || $modelType == "User" || $modelType == 'Profile') {
4500:                             $saveFlag = true;
4501:                             if ($lookupFlag) {
4502:                                 if ($_SESSION['overwrite'] == 1) { // If the user specified to overwrite, delete the old lookup
4503:                                     if ($modelType === "Fields") {
4504:                                         /**
4505:                                          * Leave fields intact; the record information would be deleted when
4506:                                          * the column is removed by Fields' afterDelete hook.
4507:                                          */
4508:                                         continue;
4509:                                     }
4510:                                     $lookup->disableBehavior('changelog');
4511:                                     $lookup->delete();
4512:                                 } else {
4513:                                     $saveFlag = false; // Otherwise, note a failure in the logging section that we were unable to overwrite a record.
4514:                                     isset($_SESSION['overwriteFailure'][$modelType]) ? $_SESSION['overwriteFailure'][$modelType] ++ : $_SESSION['overwriteFailure'][$modelType] = 1;
4515:                                 }
4516:                                 if (!$model->validate()) {
4517:                                     $saveFlag = false;
4518:                                     $failedImport = fopen($this->safePath('failedImport.csv'), 'a+');
4519:                                     $lastFailed = $_SESSION['lastFailed'];
4520:                                     if ($lastFailed != $modelType) {
4521:                                         $tempMeta = $metaData; // Keep track of the metadata of failed records
4522:                                         $tempMeta[] = $modelType;
4523:                                         fputcsv($failedImport, $tempMeta, $this->importDelimeter, $this->importEnclosure);
4524:                                     }
4525:                                     $attr = $model->attributes;
4526:                                     $tempAttributes = X2Model::model($modelType)->attributes;
4527:                                     $attr = array_merge($tempAttributes, $attr);
4528:                                     $attr[] = $modelType;
4529:                                     fputcsv($failedImport, $attr, $this->importDelimeter, $this->importEnclosure);
4530:                                     $_SESSION['lastFailed'] = $modelType; // Specify the most recent model type failure in case metadata needs to be changed
4531:                                     isset($_SESSION['failed']) ? $_SESSION['failed'] ++ : $_SESSION['failed'] = 1;
4532:                                 }
4533:                             }
4534:                             if ($saveFlag && $model->save()) {
4535:                                 if ($modelType != "Admin" && !(($modelType == "User" || $modelType == "Profile") && ($model->id == '1' || $model->username == 'api'))) {
4536:                                     // Generate a new "Imports" model in case of rollback
4537:                                     $importLink = new Imports;
4538:                                     $importLink->modelType = $modelType;
4539:                                     $importLink->modelId = $model->id;
4540:                                     $importLink->importId = $_SESSION['importId'];
4541:                                     $importLink->timestamp = time();
4542:                                     $importLink->save();
4543:                                 }
4544:                                 if ($modelType === "Fields") {
4545:                                     // If we're creating a field, we must also recreate the 
4546:                                     // respective table index
4547:                                     if (isset($model->keyType))
4548:                                         $model->createIndex($model->keyType === "UNI");
4549:                                 } else if ($modelType === "FormLayout") {
4550:                                     /*
4551:                                       Ensure default form settings are maintained. If overwrite
4552:                                       is set, the most recently imported layout will be set to
4553:                                       default, otherwise the default flags for the newly imported
4554:                                       layout will be cleared.
4555:                                      */
4556:                                     if ($_SESSION['overwrite']) {
4557:                                         if ($model->defaultView)
4558:                                             FormLayout::clearDefaultFormLayouts(
4559:                                                     'view', $model->model);
4560:                                         if ($model->defaultForm)
4561:                                             FormLayout::clearDefaultFormLayouts(
4562:                                                     'form', $model->model);
4563:                                         $model->save();
4564:                                     } else {
4565:                                         $model->defaultView = false;
4566:                                         $model->defaultForm = false;
4567:                                         $model->save();
4568:                                     }
4569:                                 }
4570:                                 // Relic of when action description wasn't a field, not sure if necessary.
4571:                                 if ($modelType == 'Actions' && isset($attributes['actionDescription'])) {
4572:                                     $model->actionDescription = $attributes['actionDescription'];
4573:                                 }
4574:                                 // Update counts in the session logging variables.
4575:                                 isset($_SESSION['counts'][$modelType]) ? $_SESSION['counts'][$modelType] ++ : $_SESSION['counts'][$modelType] = 1;
4576:                                 if ($lookupFlag) {
4577:                                     isset($_SESSION['overwriten'][$modelType]) ? $_SESSION['overwriten'][$modelType] ++ : $_SESSION['overwriten'][$modelType] = 1;
4578:                                 } else {
4579:                                     isset($_SESSION['overwriten'][$modelType])? : $_SESSION['overwriten'][$modelType] = 0;
4580:                                 }
4581:                             }
4582:                         } else {
4583:                             // Put the failed lead into the failed import CSV
4584:                             //AuxLib::debugLogR ('failed to import '.get_class ($model));
4585:                             //AuxLib::debugLogR ($model->getErrors ());
4586:                             $failedImport = fopen($this->safePath('failedImport.csv'), 'a+');
4587:                             $lastFailed = $_SESSION['lastFailed'];
4588:                             if ($lastFailed != $modelType) {
4589:                                 $tempMeta = $metaData;
4590:                                 $tempMeta[] = $modelType;
4591:                                 fputcsv($failedImport, $tempMeta, $this->importDelimeter, $this->importEnclosure);
4592:                             }
4593:                             $attr = $model->attributes;
4594:                             $tempAttributes = X2Model::model($modelType)->attributes;
4595:                             $attr = array_merge($tempAttributes, $attr);
4596:                             $attr[] = $modelType;
4597:                             fputcsv($failedImport, $attr, $this->importDelimeter, $this->importEnclosure);
4598:                             $_SESSION['lastFailed'] = $modelType;
4599:                             isset($_SESSION['failed']) ? $_SESSION['failed'] ++ : $_SESSION['failed'] = 1;
4600:                         }
4601:                     }
4602:                 } else {
4603:                     // "0" at the beginning means we reached the end of the file
4604:                     // and don't need to do another set.
4605:                     echo json_encode(array(
4606:                         0,
4607:                         json_encode($_SESSION['counts']),
4608:                         json_encode($_SESSION['overwriten']),
4609:                         json_encode($_SESSION['failed']),
4610:                         json_encode($_SESSION['overwriteFailure']),
4611:                     ));
4612:                     return;
4613:                 }
4614:             }
4615:             // Update the file offset pointer in the session.
4616:             $_SESSION['offset'] = ftell($fp);
4617:             echo json_encode(array(
4618:                 1, // The "1" indicated we need to keep going.
4619:                 json_encode($_SESSION['counts']),
4620:                 json_encode($_SESSION['overwriten']),
4621:                 json_encode($_SESSION['failed']),
4622:                 json_encode($_SESSION['overwriteFailure']),
4623:             ));
4624:         }
4625:     }
4626: 
4627:     /**
4628:      * Post-processing function for the import tool, unset session variables
4629:      * and delete the uploaded data file.
4630:      */
4631:     public function actionCleanUpImport() {
4632:         unlink($this->safePath());
4633:         unset($_SESSION['counts']);
4634:         unset($_SESSION['overwriten']);
4635:         unset($_SESSION['model']);
4636:         unset($_SESSION['overwrite']);
4637:         unset($_SESSION['metaData']);
4638:         unset($_SESSION['failed']);
4639:         unset($_SESSION['lastFailed']);
4640:         unset($_SESSION['overwriteFailure']);
4641:     }
4642: 
4643:     /**
4644:      * Post-processing for the import process to clean out the SESSION vars.
4645:      */
4646:     public function actionCleanUpModelImport() {
4647:         unset($_SESSION['tags']);
4648:         unset($_SESSION['override']);
4649:         unset($_SESSION['comment']);
4650:         unset($_SESSION['leadRouting']);
4651:         unset($_SESSION['createRecords']);
4652:         unset($_SESSION['imported']);
4653:         unset($_SESSION['failed']);
4654:         unset($_SESSION['created']);
4655:         unset($_SESSION['importMap']);
4656:         unset($_SESSION['offset']);
4657:         unset($_SESSION['metaData']);
4658:         unset($_SESSION['fields']);
4659:         unset($_SESSION['x2attributes']);
4660:         unset($_SESSION['model']);
4661:         if (file_exists($path = $this->safePath('data.csv'))) {
4662:             unlink($path);
4663:         }
4664:         if (file_exists($path = $this->safePath('importMapping.json'))) {
4665:             unlink($path);
4666:         }
4667:     }
4668: 
4669:     /**
4670:      * Control settings for the updater
4671:      *
4672:      * This method controls the update interval setting for the application.
4673:      */
4674:     public function actionUpdaterSettings() {
4675:         $admin = &Yii::app()->settings;
4676:         // Save new updater cron settings in crontab
4677:         $cf = new CronForm;
4678:         $cf->jobs = array(
4679:             'app_update' => array(
4680:                 'cmd' => Yii::app()->basePath . DIRECTORY_SEPARATOR . 'yiic update app --lock=1 &>/dev/null',
4681:                 'desc' => Yii::t('admin', 'Automatic software updates cron job'),
4682:             ),
4683:         );
4684:         if (isset($_POST['Admin'])) {
4685:             $admin->setAttributes($_POST['Admin']);
4686:             foreach (array('unique_id', 'edition') as $var)
4687:                 if (isset($_POST['unique_id']))
4688:                     $admin->$var = $_POST[$var];
4689:             if ($admin->save()) {
4690:                 if (isset($_POST['cron'])) {
4691:                     // Save new updater cron settings in crontab
4692:                     $cf->save($_POST);
4693:                 } else {
4694:                     // Delete remaining jobs
4695:                     $cf->save(array());
4696:                 }
4697:                 $this->redirect('updaterSettings');
4698:             }
4699:         }
4700:         foreach ($cf->jobs as $tag => $attributes) {
4701:             $commands[$tag] = $attributes['cmd'];
4702:         }
4703:         if (isset($_POST['cron'])) {
4704:             // Save new updater cron settings in crontab
4705:             $cf->save($_POST);
4706:         }
4707:         $this->render('updaterSettings', array(
4708:             'model' => $admin,
4709:             'displayCmds' => $commands
4710:         ));
4711:     }
4712: 
4713:     /**
4714:      * Respond to a request with a specified status code and body.
4715:      *
4716:      * @param integer $status The HTTP status code.
4717:      * @param string $body The body of the response message
4718:      * @param string $content_type The response mimetype.
4719:      */
4720:     private function _sendResponse($status = 200, $body = '', $content_type = 'text/html') {
4721:         // set the status
4722:         $status_header = 'HTTP/1.1 ' . $status . ' ' . $this->_getStatusCodeMessage($status);
4723:         header($status_header);
4724:         // and the content type
4725:         header('Content-type: ' . $content_type);
4726: 
4727:         // pages with body are easy
4728:         if ($body != '') {
4729:             // send the body
4730:             echo $body;
4731:             exit;
4732:         }
4733:         // we need to create the body if none is passed
4734:         else {
4735:             // create some body messages
4736:             $message = '';
4737: 
4738:             // this is purely optional, but makes the pages a little nicer to read
4739:             // for your users.  Since you won't likely send a lot of different status codes,
4740:             // this also shouldn't be too ponderous to maintain
4741:             switch ($status) {
4742:                 case 401:
4743:                     $message = 'You must be authorized to view this page.';
4744:                     break;
4745:                 case 404:
4746:                     $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';
4747:                     break;
4748:                 case 500:
4749:                     $message = 'The server encountered an error processing your request.';
4750:                     break;
4751:                 case 501:
4752:                     $message = 'The requested method is not implemented.';
4753:                     break;
4754:             }
4755: 
4756:             // servers don't always have a signature turned on
4757:             // (this is an apache directive "ServerSignature On")
4758:             $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];
4759: 
4760:             // this should be templated in a real-world solution
4761:             $body = '
4762:     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
4763:     <html>
4764:     <head>
4765:         <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
4766:         <title>' . $status . ' ' . $this->_getStatusCodeMessage($status) . '</title>
4767:     </head>
4768:     <body>
4769:         <h1>' . $this->_getStatusCodeMessage($status) . '</h1>
4770:         <p>' . $message . '</p>
4771:         <hr />
4772:         <address>' . $signature . '</address>
4773:     </body>
4774:     </html>';
4775: 
4776:             echo $body;
4777:             exit;
4778:         }
4779:     }
4780: 
4781:     /**
4782:      * Obtain an appropriate message for a given HTTP response code.
4783:      *
4784:      * @param integer $status
4785:      * @return string
4786:      */
4787:     private function _getStatusCodeMessage($status) {
4788:         // these could be stored in a .ini file and loaded
4789:         // via parse_ini_file()... however, this will suffice
4790:         // for an example
4791:         $codes = Array(
4792:             200 => 'OK',
4793:             400 => 'Bad Request',
4794:             401 => 'Unauthorized',
4795:             402 => 'Payment Required',
4796:             403 => 'Forbidden',
4797:             404 => 'Not Found',
4798:             408 => 'Request Timeout',
4799:             500 => 'Internal Server Error',
4800:             501 => 'Not Implemented',
4801:         );
4802:         return (isset($codes[$status])) ? $codes[$status] : '';
4803:     }
4804: 
4805:     /**
4806:      * View the changelogs.
4807: 
4808:       public function actionViewLogs() {
4809:       $this->render('viewLogs');
4810:       } */
4811: 
4812:     /**
4813:      * Prints an error message explaing what has gone wrong when the classes are missing.
4814:      * @param array $classes The missing dependencies
4815:      */
4816:     public function missingClassesException($classes) {
4817:         $message = Yii::t('admin', 'One or more dependencies of AdminController are missing and could not be automatically retrieved. They are {classes}', array('{classes}' => implode(', ', $classes)));
4818:         $message .= "\n\n" . Yii::t('admin', 'To diagnose this error, please upload and run the requirements check script on your server.');
4819:         $message .= "\nhttps://x2planet.com/installs/requirements.php";
4820:         $message .= "\n\n" . Yii::t('admin', 'The error is most likely due to one of the following things:');
4821:         $message .= "\n(1) " . Yii::t('admin', 'PHP processes run by the web server do not have permission to create or modify files');
4822:         $message .= "\n(2) " . Yii::t('admin', 'x2planet.com and raw.github.com are currently unavailable');
4823:         $message .= "\n(3) " . Yii::t('admin', 'This web server has no outbound internet connection. This could be because it is behind a firewall that does not permit outbound connections, operating within a private network with broken domain name resolution, or with no outbound route.');
4824:         $message .= "\n\n" . Yii::t('admin', 'To stop this error from occurring, if the problem persists, restore the file {adminController} to the copy from your version of X2Engine:', array('{adminController}' => 'protected/controllers/AdminController.php'));
4825:         $message .= "\n" . "https://raw.github.com/X2Engine/X2Engine/" . Yii::app()->params->version . "/x2engine/protected/controllers/AdminController.php";
4826:         $this->error500($message);
4827:     }
4828: 
4829:     /**
4830:      * Function written by Matthew to display a tree-like hierarchy of the roles
4831:      * Legend:
4832:      *  blue: roles (type 0)
4833:      *  white: tasks (type 1)
4834:      *  red: actions (type 2)
4835:      */
4836:     public function actionAuthGraph() {
4837: 
4838:         if (!Yii::app()->params->isAdmin)
4839:             return;
4840: 
4841:         $allTasks = array();
4842: 
4843:         $authGraph = array();
4844: 
4845:         $taskNames = Yii::app()->db->createCommand()
4846:                 ->select('name')
4847:                 ->from('x2_auth_item')
4848:                 ->where('type=1')
4849:                 ->queryColumn();
4850: 
4851:         foreach ($taskNames as $task) {
4852:             $children = Yii::app()->db->createCommand()
4853:                     ->select('child')
4854:                     ->from('x2_auth_item_child')
4855:                     ->where('parent=:parent', array(':parent' => $task))
4856:                     ->queryColumn();
4857: 
4858:             foreach ($children as $child)
4859:                 $allTasks[$task][$child] = array();
4860:         }
4861: 
4862:         $bizruleTasks = Yii::app()->db->createCommand()
4863:                 ->select('name')
4864:                 ->from('x2_auth_item')
4865:                 ->where('bizrule IS NOT NULL')
4866:                 ->queryColumn();
4867: 
4868:         function buildGraph($task, &$allTasks, &$authGraph) {
4869: 
4870:             if (!isset($allTasks[$task]) || empty($allTasks[$task])) {
4871:                 return array();
4872:             } else {
4873:                 $children = array();
4874: 
4875:                 foreach (array_keys($allTasks[$task]) as $child) {
4876: 
4877:                     if (isset($authGraph[$child]) && $authGraph[$child] === false)
4878:                         continue;
4879: 
4880:                     $childGraph = buildGraph($child, $allTasks, $authGraph);
4881: 
4882:                     $children[$child] = $childGraph;
4883:                     $authGraph[$child] = false; // this is a child task, remove it from the top level
4884:                 }
4885:                 return $children;
4886:             }
4887:         }
4888: 
4889:         foreach (array_keys($allTasks) as $task)
4890:             $authGraph[$task] = buildGraph($task, $allTasks, $authGraph);
4891: 
4892:         foreach (array_keys($authGraph) as $key) {
4893:             if (empty($authGraph[$key]))
4894:                 unset($authGraph[$key]);
4895:         }
4896: 
4897:         $this->render('authGraph', array('authGraph' => $authGraph, 'bizruleTasks' => $bizruleTasks));
4898:     }
4899: 
4900:     /**
4901:      * Last-resort, built-in, fail-resistant copy method
4902:      *
4903:      * Copy method used in the case that FileUtil is not yet available (i.e. if
4904:      * AdminController was downloaded in an auto-update by a much older version
4905:      * but nothing else). Returns true on success and false on failure.
4906:      *
4907:      * @param string $remoteFile URL of file to fetch
4908:      * @param string $localFile Path to local destination
4909:      * @param boolean $curl Whether to use CURL
4910:      * @return boolean
4911:      */
4912:     public function copyRemote($remoteFile, $localFile, $curl) {
4913:         $this->checkRemoteMethods();
4914:         if (!$curl) {
4915:             $context = stream_context_create(array(
4916:                 'http' => array(
4917:                     'timeout' => 15  // Timeout in seconds
4918:             )));
4919:             return copy($remoteFile, $localFile, $context) !== false;
4920:         } else {
4921:             // Try using CURL
4922:             $ch = curl_init($remoteFile);
4923:             $curlopt = array(
4924:                 CURLOPT_RETURNTRANSFER => 1,
4925:                 CURLOPT_RETURNTRANSFER => 1,
4926:                 CURLOPT_BINARYTRANSFER => 1,
4927:                 CURLOPT_POST => 0,
4928:                 CURLOPT_TIMEOUT => 15
4929:             );
4930:             curl_setopt_array($ch, $curlopt);
4931:             $contents = curl_exec($ch);
4932:             if ((bool) $contents) {
4933:                 return file_put_contents($localFile, $contents) !== false;
4934:             } else
4935:                 return false;
4936:         }
4937:     }
4938: 
4939:     /**
4940:      * Magic getter for "noRemoteAccess" property.
4941:      *
4942:      * If true, signifies that there is no possible way to retrieve remote files.
4943:      * @return boolean
4944:      */
4945:     public function getNoRemoteAccess() {
4946:         if (!isset($this->_noRemoteAccess))
4947:             $this->_noRemoteAccess = !extension_loaded('curl') && (
4948:                     in_array(ini_get('allow_url_fopen'), array(0, 'Off', 'off')) || !(function_exists('file_get_contents') && function_exists('copy'))
4949:                     );
4950:         return $this->_noRemoteAccess;
4951:     }
4952: 
4953:     /**
4954:      * Check whether it is possible to retrieve remote files.
4955:      */
4956:     public function checkRemoteMethods() {
4957:         if ($this->noRemoteAccess)
4958:             $this->error500(Yii::t('admin', 'X2Engine needs to retrieve one or more remote files, but no remote access methods are available on this web server, because allow_url_fopen is disabled and the CURL extension is missing.'));
4959:     }
4960: 
4961:     /**
4962:      * Explicit, attention-grabbing error message w/o bug reporter.
4963:      *
4964:      * This is intended for errors that are NOT bugs, but that arise from server
4965:      * malconfiguration and/or missing requirements for running X2Engine, as a
4966:      * last-ditch effort to fail gracefully.
4967:      * @param type $message
4968:      */
4969:     public function error500($message) {
4970:         $app = Yii::app();
4971:         $email = Yii::app()->params->adminEmail;
4972:         $inAction = $this->action instanceof CAction;
4973:         if ($app->params->hasProperty('admin')) {
4974:             if ($app->params->admin->hasProperty('emailFromAddr'))
4975:                 $email = $app->params->admin->emailFromAddr;
4976:         } else if ($app->hasProperty('settings')) {
4977:             if ($app->settings->hasProperty('emailFromAddr')) {
4978:                 $email = $app->settings->emailFromAddr;
4979:             }
4980:         }
4981:         $inAction = @is_subclass_of($this->action, 'CAction');
4982:         if ($inAction) {
4983:             $data = array(
4984:                 'scenario' => 'error',
4985:                 'message' => Yii::t('admin', "Cannot run {action}.", array('{action}' => $this->action->id)),
4986:                 'longMessage' => str_replace("\n", "<br />", $message),
4987:             );
4988:             $this->render('updater', $data);
4989:             Yii::app()->end();
4990:         } else {
4991:             $data = array(
4992:                 'time' => time(),
4993:                 'admin' => $email,
4994:                 'version' => Yii::getVersion(),
4995:                 'message' => $message
4996:             );
4997:             header("HTTP/1.1 500 Internal Server Error");
4998:             $this->renderPartial('system.views.error500', array('data' => $data));
4999:         }
5000:         Yii::app()->end();
5001:     }
5002: 
5003:     /**
5004:      * Change the application name
5005:      */
5006:     public function actionChangeApplicationName() {
5007:         $model = Admin::model()->findByPk(1);
5008:         if (isset($_POST['Admin'])) {
5009:             $model->setAttributes($_POST['Admin']);
5010:             if ($model->save()) {
5011:                 $this->redirect('index');
5012:             }
5013:         }
5014:         $this->render('changeApplicationName', array(
5015:             'model' => $model
5016:         ));
5017:     }
5018: 
5019:     
5020: 
5021:     /**
5022:      * Echo a list of model attributes as a dropdown.
5023:      *
5024:      * This method is called via AJAX as a part of creating notification criteria.
5025:      * It takes the model or module name as POST data and returns a list of dropdown
5026:      * options consisting of the fields available to that model.
5027:      */
5028:     public function actionGetAttributes() {
5029:         $data = array();
5030:         $type = null;
5031: 
5032:         if (isset($_POST['Criteria']['modelType']))
5033:             $type = ucfirst($_POST['Criteria']['modelType']);
5034:         if (isset($_POST['Fields']['modelName']))
5035:             $type = $_POST['Fields']['modelName'];
5036: 
5037:         if (isset($type)) {
5038:             if ($type == 'Marketing')
5039:                 $type = 'Campaign';
5040:             elseif ($type == 'Quotes')
5041:                 $type = 'Quote';
5042:             elseif ($type == 'Products')
5043:                 $type = 'Product';
5044:             elseif ($type == 'Opportunities')
5045:                 $type = 'Opportunity';
5046: 
5047:             foreach (X2Model::model('Fields')->findAllByAttributes(array('modelName' => $type)) as $field) {
5048:                 if ($field->fieldName != 'id') {
5049:                     if (isset($_POST['Criteria']))
5050:                         $data[$field->fieldName] = $field->attributeLabel;
5051:                     else
5052:                         $data[$field->id] = $field->attributeLabel;
5053:                 }
5054:             }
5055:         }
5056:         asort($data);
5057:         $data = array('' => '-') + $data;
5058:         $htmlOptions = array();
5059:         echo CHtml::listOptions('', $data, $htmlOptions);
5060:     }
5061: 
5062:     
5063:     
5064:     /**
5065:      * Fix email templates broken by the 5.1->5.2/5.3 media module changes.
5066:      */
5067:     public function actionConvertEmailTemplates(){
5068:         $status = null;
5069:         if(isset($_POST['yt0'])){
5070:             $docs = Docs::model()->findAllByAttributes(array('type'=>'email'));
5071:             $converted = 0;
5072:             foreach($docs as $doc){
5073:                 $changed = false;
5074:                 preg_match_all('|<img(.*?)src="(.*?)"(.*?)/?>|ism', $doc->text, $matches);
5075:                 $serverBasePath = Yii::app()->request->getServerName().Yii::app()->baseUrl;
5076:                 foreach($matches[2] as $filePath){
5077:                     if(strpos($filePath, $serverBasePath) !== false) {
5078:                         $uploadPath = str_replace($serverBasePath,'',$filePath);
5079:                         $pieces = explode('/',$uploadPath);
5080:                         $fileName = $pieces[sizeof($pieces)-1];
5081:                         $mediaObj = Media::model()->findByAttributes(array('fileName'=>$fileName));
5082:                         if(isset($mediaObj)){
5083:                             $doc->text = preg_replace('|<img(.*?)src="'.preg_quote($filePath).'"(.*?)/?>|ism','<img\1src="'.$mediaObj->getPublicUrl().'"\2/>',$doc->text);
5084:                             $changed = true;
5085:                         }
5086:                     }
5087:                 }
5088:                 if($changed){
5089:                     $doc->save();
5090:                     $converted++;
5091:                 }
5092:             }
5093:             $status = $converted;
5094:         }
5095:         $this->render('convertEmailTemplates',array(
5096:             'status'=>$status,
5097:         ));
5098:     }
5099:     
5100:     
5101: 
5102:     
5103: }
5104: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0