diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..0ed8662 --- /dev/null +++ b/.htaccess @@ -0,0 +1,5 @@ + + RewriteEngine on + RewriteRule ^$ webroot/ [L] + RewriteRule (.*) webroot/$1 [L] + \ No newline at end of file diff --git a/app_controller.php b/app_controller.php new file mode 100644 index 0000000..5ed9976 --- /dev/null +++ b/app_controller.php @@ -0,0 +1,881 @@ +params['dev'] = false; + $this->params['admin'] = false; + parent::__construct(); + } + + function sideMenuLinks() { + // Stupid Cake... our constructor sets admin/dev, + // but cake stomps it somewhere along the way + // after constructing the CakeError controller. + if ($this->name === 'CakeError') { + $this->params['dev'] = false; + $this->params['admin'] = false; + } + + $menu = array(); + $menu[] = array('name' => 'Common', 'header' => true); + $menu[] = array('name' => 'Site Map', 'url' => array('controller' => 'maps', 'action' => 'view', 1)); + $menu[] = array('name' => 'Units', 'url' => array('controller' => 'units', 'action' => 'index')); + $menu[] = array('name' => 'Leases', 'url' => array('controller' => 'leases', 'action' => 'index')); + $menu[] = array('name' => 'Customers', 'url' => array('controller' => 'customers', 'action' => 'index')); + $menu[] = array('name' => 'Deposits', 'url' => array('controller' => 'transactions', 'action' => 'deposit')); + + if ($this->params['admin']) { + $menu[] = array('name' => 'Admin', 'header' => true); + $menu[] = array('name' => 'Accounts', 'url' => array('controller' => 'accounts', 'action' => 'index')); + $menu[] = array('name' => 'Contacts', 'url' => array('controller' => 'contacts', 'action' => 'index')); + $menu[] = array('name' => 'Ledgers', 'url' => array('controller' => 'ledgers', 'action' => 'index')); + $menu[] = array('name' => 'Tenders', 'url' => array('controller' => 'tenders', 'action' => 'index')); + $menu[] = array('name' => 'Transactions', 'url' => array('controller' => 'transactions', 'action' => 'index')); + $menu[] = array('name' => 'Ldgr Entries', 'url' => array('controller' => 'ledger_entries', 'action' => 'index')); + $menu[] = array('name' => 'Stmt Entries', 'url' => array('controller' => 'statement_entries', 'action' => 'index')); + $menu[] = array('name' => 'New Ledgers', 'url' => array('controller' => 'accounts', 'action' => 'newledger')); + $menu[] = array('name' => 'Assess Charges', 'url' => array('controller' => 'leases', 'action' => 'assess_all')); + } + + if ($this->params['dev']) { + $menu[] = array('name' => 'Development', 'header' => true); + $menu[] = array('name' => 'Un-Nuke', 'url' => '#', 'htmlAttributes' => + array('onclick' => '$(".pr-section").show(); return false;')); + $menu[] = array('name' => 'New Ledgers', 'url' => array('controller' => 'accounts', 'action' => 'newledger')); + //array('name' => 'RESET DATA', 'url' => array('controller' => 'accounts', 'action' => 'reset_data')); + } + + return $menu; + } + + function beforeFilter() { + $this->params['dev'] = + (!empty($this->params['dev_route'])); + $this->params['admin'] = + (!empty($this->params['admin_route']) || !empty($this->params['dev_route'])); + + if (!$this->params['dev']) + Configure::write('debug', '0'); + } + + function beforeRender() { + $this->set('sidemenu', $this->sideMenuLinks()); + } + + function redirect($url, $status = null, $exit = true) { + // OK, since the controller will not be able to + // utilize our overriden url function in AppHelper, + // we'll have to do it manually here. + App::import('Helper', 'Html'); + $url = HtmlHelper::url($url, true); + + if (headers_sent()) { + // If we've already sent the headers, it's because + // we're debugging, and our debug output has gotten + // out before the redirect. That's probably a good + // thing, as we don't typically want pages to be + // jerked out from under us while trying to read + // the debug output. So, since we can't redirect + // anyway, we may as well go with the flow and just + // render this page instead, using an empty template + $this->set('message', + ("Intended redirect:


" . + ''.$url.'')); + + echo $this->render('/empty'); + if ($exit) + $this->_stop(); + } + + return parent::redirect($url, $status, $exit); + } + + function reset_data() { + $this->layout = null; + $this->autoLayout = false; + $this->autoRender = false; + Configure::write('debug', '0'); + $script = $_SERVER['DOCUMENT_ROOT'] . '/pmgr/build.cmd'; + echo "

" . date('r') . "\n"; + //echo "

Script: $script" . "\n"; + $db = & $this->Account->getDataSource(); + $script .= ' "' . $db->config['database'] . '"'; + $script .= ' "' . $db->config['login'] . '"'; + $script .= ' "' . $db->config['password'] . '"'; + $handle = popen($script . ' 2>&1', 'r'); + //echo "

Handle: $handle; " . gettype($handle) . "\n"; + echo "

\n";
+    while (($read = fread($handle, 2096))) {
+      echo $read;
+    }
+    echo "
\n"; + pclose($handle); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * helper: gridView + * - called by derived controllers to create an index listing + */ + + function gridView($title, $action = null, $element = null) { + $this->set('title', $title); + // The resulting page will contain a grid, which will + // use ajax to obtain the actual data for this action + $this->set('action', $action ? $action : $this->params['action']); + $this->render('/elements/' . ($element ? $element : $this->params['controller'])); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + ************************************************************************** + ************************************************************************** + * action: gridData + * - Fetches the actual data requested by grid as XML + */ + + function gridData() { + // Grab a copy of the parameters that control this request + $params = array(); + if (isset($this->params['url']) && is_array($this->params['url'])) + $params = $this->params['url']; + + // Do any preliminary setup necessary + $this->gridDataSetup($params); + + // Get the top level model for this grid + $model = $this->gridDataModel($params); + + // Get the number of records prior to pagination + $count = $this->gridDataCount($params, $model); + + // Determine pagination configuration (and save to $params) + $pagination = $this->gridDataPagination($params, $model, $count); + + // Retreive the appropriate subset of data + $records = $this->gridDataRecords($params, $model, $pagination); + + // Post process the records + $this->gridDataPostProcess($params, $model, $records); + + // Output the resulting record set + $this->gridDataOutput($params, $model, $records, $pagination); + + // Call out to finalize everything + $this->gridDataFinalize($params); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + ************************************************************************** + ************************************************************************** + * virtual: gridData* functions + * - These set up the context for the grid data, and will + * need to be overridden in the derived class for anything + * other than the most basic of grids. + */ + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * gridData SETUP / CLEANUP + */ + + function gridDataModel(&$params) { + return $this->{$this->modelClass}; + } + + function gridDataSetup(&$params) { + // Debug only if requested + $params['debug'] = !empty($this->passedArgs['debug']); + + if ($params['debug']) { + ob_start(); + } + else { + $this->layout = null; + $this->autoLayout = false; + $this->autoRender = false; + Configure::write('debug', '0'); + } + + // Establish some defaults (except for serialized items) + $params = array_merge(array('page' => 1, + 'rows' => 20), + $params); + + // Unserialize our complex structure of post data. + // This SHOULD always be set, except when debugging + if (isset($params['post'])) + $params['post'] = unserialize($params['post']); + else + $params['post'] = array(); + + // Unserialize our complex structure of dynamic post data + if (isset($params['dynamic_post'])) + $params['dynamic_post'] = unserialize($params['dynamic_post']); + else + $params['dynamic_post'] = null; + + // Unserialize our complex structure of dynamic post data + if (isset($params['dynamic_post_replace'])) + $params['dynamic_post_replace'] = unserialize($params['dynamic_post_replace']); + else + $params['dynamic_post_replace'] = null; + + // Merge the static and dynamic post data + if (!empty($params['dynamic_post'])) + $params['post'] = array_merge_recursive($params['post'], $params['dynamic_post']); + if (!empty($params['dynamic_post_replace'])) + $params['post'] = array_merge($params['post'], $params['dynamic_post_replace']); + + // This SHOULD always be set, except when debugging + if (!isset($params['post']['fields'])) + $params['post']['fields'] = array($this->{$this->modelClass}->alias + .'.'. + $this->{$this->modelClass}->primaryKey); + + // Make sure the action parameter at least exists, and + // promote it to the top level (since it drives the operation). + if (isset($params['post']['action'])) + $params['action'] = $params['post']['action']; + else + $params['action'] = null; + } + + function gridDataFinalize(&$params) { + if ($params['debug']) { + $xml = ob_get_contents(); + ob_end_clean(); + + $xml = preg_replace("/&/", "&", $xml); + $xml = preg_replace("//", ">", $xml); + echo ("\n
\n$xml\n
\n"); + } + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * gridData COUNTING + */ + + function gridDataCount(&$params, &$model) { + // Establish the tables and conditions for counting + $query = array_intersect_key($this->gridDataCountTableSet($params, $model), + array('link'=>1, 'contain'=>1)); + + // Conditions for the count + $query['conditions'] = $this->gridDataCountConditionSet($params, $model); + + // Grouping (which would not be typical) + $query['group'] = $this->gridDataCountGroup($params, $model); + + // DEBUG PURPOSES ONLY! + $params['count_query'] = $query; + + // Get the number of records prior to pagination + return $this->gridDataCountExecute($params, $model, $query); + } + + function gridDataCountExecute(&$params, &$model, $query) { + return $model->find('count', $query); + } + + function gridDataCountTables(&$params, &$model) { + // Same tables for counting as for retreiving + return $this->gridDataTables($params, $model); + } + + function gridDataCountTableSet(&$params, &$model) { + // Preliminary set of tables + $query = array_intersect_key($this->gridDataCountTables($params, $model), + array('link'=>1, 'contain'=>1)); + + // Perform filtering based on user request: $params['post']['filter'] + return array_intersect_key($this->gridDataFilterTables($params, $model, $query), + array('link'=>1, 'contain'=>1)); + } + + function gridDataCountConditions(&$params, &$model) { + // Same conditions for counting as for retreiving + return $this->gridDataConditions($params, $model); + } + + function gridDataCountConditionSet(&$params, &$model) { + // Conditions for the count + $conditions = $this->gridDataCountConditions($params, $model); + + // Perform filtering based on user request: $params['post']['filter'] + return $this->gridDataFilterConditions($params, $model, $conditions); + } + + function gridDataCountGroup(&$params, &$model) { + // Grouping will screw up the count, since it + // causes the results to be split into chunks + // based on the GROUP BY clause. We can't have + // more than one row of data in the count query, + // just a _single_ row with the actual count. + return null; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * gridData FILTERING + */ + + function gridDataFilterTables(&$params, &$model, $query) { + if (isset($query['contain'])) + $link = 'contain'; + else + $link = 'link'; + + if (empty($params['post']['filter'])) + return $query; + + foreach ($params['post']['filter'] AS $filter => $filter_value) { + $split = $this->gridDataFilterSplit($params, $model, $filter, $filter_value); + +/* pr(array('AppController::gridDataFilterTable' => */ +/* array('checkpoint' => "Filter split") */ +/* + compact('split'))); */ + + $table = $this->gridDataFilterTablesTable($params, $model, $split['table']); + if (!$table) + continue; + + $config = $this->gridDataFilterTablesConfig($params, $model, $split['table']); + +/* pr(array('AppController::gridDataFilterTable' => */ +/* array('checkpoint' => "Add filter config to query") */ +/* + array('query[link]' => $query[$link]) */ +/* + compact('config'))); */ + + // If the table is already part of the query, append to it + if ($table == $model->alias) { + $query[$link] = + array_merge_recursive(array_diff_key($config, array('fields'=>1)), + $query[$link]); + } + elseif (isset($query[$link][$table])) { + $query[$link][$table] = + array_merge_recursive($config, $query[$link][$table]); + } + elseif (in_array($table, $query[$link])) { + // REVISIT : 20090727 + // This presents a dilema. $config may specify fields + // as array(), whereas the user has already indicated + // they desire ALL fields (by the fact that table is + // a member of the query['link'] array without anything + // specified). However, $config _could_ specify a + // non-standard field in the array, such as using SUM() + // or other equation. In that case, we'd have to add + // in all the fields of table to the array. That + // wouldn't be very hard, but I'll ignore it at the + // moment and wait until it's needed. + $key = array_search($table, $query[$link]); + $query[$link][$table] = array('fields' => null) + $config; + unset($query[$link][$key]); + } + else { + // Table is NOT already part of the query... set it now + $query[$link][$table] = $config; + } + +/* pr(array('post-filter-table-config' => */ +/* array('query[link]' => $query[$link]))); */ + + } + + return $query; + } + + function gridDataFilterTablesTable(&$params, &$model, $table) { + // By default, don't add anything if the filter + // condition occurs on the actual model table + if ($table == $model->alias) + return null; + return $this->gridDataFilterTableName($params, $model, $table); + } + + function gridDataFilterTablesConfig(&$params, &$model, $table) { + return array('fields' => array()); + } + + function gridDataFilterConditions(&$params, &$model, $conditions) { + if (empty($params['post']['filter'])) + return $conditions; + + foreach ($params['post']['filter'] AS $filter => $filter_value) { + $split = $this->gridDataFilterSplit($params, $model, $filter, $filter_value); + + $table = $this->gridDataFilterConditionsTable($params, $model, $split['table']); + $key = $this->gridDataFilterConditionsKey($params, $model, $split['table'], $split['field']); + if (!$key) + continue; + + $conditions[] + = $this->gridDataFilterConditionsStatement($params, $model, $table, $key, + array_intersect_key + ($split, + array('value'=>1, + 'value_present'=>1))); + } + + return $conditions; + } + + function gridDataFilterConditionsTable(&$params, &$model, $table) { + return $this->gridDataFilterTableName($params, $model, $table); + } + + function gridDataFilterConditionsKey(&$params, &$model, $table, $id) { + // REVISIT : 20090722 + // When $id is null, we could instantiate the table, + // and use the _actual_ primary key. However, I don't + // expect that functionality to be used, and will just + // stick with 'id' for now. + return $id ? $id : 'id'; + } + + function gridDataFilterConditionsStatement(&$params, &$model, $table, $key, $value) { + $key = (empty($table)?"":"{$table}.") . $key; + if (isset($value['value_present']) && $value['value_present']) + return array($key => $value['value']); + else + return array($key); + } + + function gridDataFilterSplit(&$params, &$model, $filter, $value) { + $value_present = true; + if (!is_string($filter)) { + // only a filter condition was set, not filter=>value + $filter = $value; + $value = null; + $value_present = false; + } + + $table = $field = null; + if (preg_match("/^\w+\.\w+(\s*[!<>=]+)?$/", $filter)) { + list($table, $field) = explode(".", $filter); + } + elseif (preg_match('/^[A-Z][A-Za-z]*$/', $filter)) { + $table = $filter; + $field = null; + } + elseif (preg_match('/^\w+(\s*[!<>=]+)?$/', $filter)) { + $table = $model->alias; + $field = $filter; + } + else { + // $filter must be a function or other complicated condition + $table = null; + $field = $filter; + } + + return compact('table', 'field', 'value', 'value_present'); + } + + function gridDataFilterTableName(&$params, &$model, $table) { + return Inflector::camelize($table); + } + + + /************************************************************************** + ************************************************************************** + * gridData PAGINATION + */ + + function gridDataPagination(&$params, &$model, $record_count) { + // Retrieve, or calculate, all parameters necesssary for + // pagination. Verify the passed in parameters are valid. + + $limit = $params['rows'] > 0 ? $params['rows'] : 10; + $total = ($record_count < 0) ? 0 : ceil($record_count/$limit); + $page = ($params['page'] <= 1) ? 1 : (($params['page'] > $total) ? $total : $params['page']); + $start = $limit * ($page - 1); + + return compact('record_count', 'limit', 'page', 'start', 'total'); + } + + + /************************************************************************** + ************************************************************************** + * gridData RETREIVAL + */ + + function gridDataRecords(&$params, &$model, $pagination) { + // Establish the tables for this query + $query = array_intersect_key($this->gridDataTableSet($params, $model), + array('link'=>1, 'contain'=>1)); + + // Specify the fields for the query + $query['fields'] = $this->gridDataFields($params, $model); + + // Conditions of the query + $query['conditions'] = $this->gridDataConditionSet($params, $model); + + // Data record grouping + $query['group'] = $this->gridDataGroup($params, $model); + + // The subset of data based on pagination + $query['limit'] = $this->gridDataLimit($params, $model, $pagination['start'], $pagination['limit']); + + // Ordering based on user request + $query['order'] = $this->gridDataOrder($params, $model, + isset($params['sidx']) ? $params['sidx'] : null, + isset($params['sord']) ? $params['sord'] : null); + + // DEBUG PURPOSES ONLY! + $params['query'] = $query; + + return $this->gridDataRecordsExecute($params, $model, $query); + } + + function gridDataRecordsExecute(&$params, &$model, $query) { + return $model->find('all', $query); + } + + function gridDataTables(&$params, &$model) { + return array('link' => array()); + } + + function gridDataTableSet(&$params, &$model) { + // Preliminary set of tables + $query = array_intersect_key($this->gridDataTables($params, $model), + array('link'=>1, 'contain'=>1)); + + // Perform filtering based on user request: $params['post']['filter'] + $query = array_intersect_key($this->gridDataFilterTables($params, $model, $query), + array('link'=>1, 'contain'=>1)); + + return $query; + } + + + function gridDataConditions(&$params, &$model) { + $searches = array(); + + if (isset($params['_search']) && $params['_search'] === 'true') { + if (isset($params['searchOper'])) { + $searches[] = array('op' => $params['searchOper'], + 'field' => $params['searchField'], + 'value' => $params['searchString']); + } + else { + // DOH! Crappy mechanism puts toolbar search terms + // directly into params as name/value pairs. No + // way to know which elements of params are search + // terms, so skipping this at the moment. + } + } + elseif (isset($params['filt']) && $params['filt']) { + $searches[] = array('op' => 'bw', + 'field' => $params['filtField'], + 'value' => $params['filtValue']); + } + + $ops = array('eq' => array('op' => null, 'pre' => '', 'post' => ''), + 'ne' => array('op' => '<>', 'pre' => '', 'post' => ''), + 'lt' => array('op' => '<', 'pre' => '', 'post' => ''), + 'le' => array('op' => '<=', 'pre' => '', 'post' => ''), + 'gt' => array('op' => '>', 'pre' => '', 'post' => ''), + 'ge' => array('op' => '>=', 'pre' => '', 'post' => ''), + 'bw' => array('op' => 'LIKE', 'pre' => '', 'post' => '%'), + 'ew' => array('op' => 'LIKE', 'pre' => '%', 'post' => ''), + 'cn' => array('op' => 'LIKE', 'pre' => '%', 'post' => '%'), + ); + + $conditions = array(); + foreach ($searches AS $search) { + $op = $ops[$search['op']]; + $field = $search['field'] . ($op['op'] ? ' '.$op['op'] : ''); + $value = $op['pre'] . $search['value']. $op['post']; + $conditions[] = array($field => $value); + } + + if (isset($params['action']) && $params['action'] === 'idlist') { + if (count($params['post']['idlist'])) + $conditions[] = array($model->alias.'.'.$model->primaryKey => $params['post']['idlist']); + else + $conditions[] = '0=1'; + } + + return $conditions; + } + + function gridDataConditionSet(&$params, &$model) { + // Conditions for record retrieval + $conditions = $this->gridDataConditions($params, $model); + + // Perform filtering based on user request: $params['post']['filter'] + return $this->gridDataFilterConditions($params, $model, $conditions); + } + + function gridDataFields(&$params, &$model) { + $db = &$model->getDataSource(); + $fields = $db->fields($model, $model->alias); + return $fields; + } + + function gridDataGroup(&$params, &$model) { + return $model->alias.'.'.$model->primaryKey; + } + + function gridDataOrder(&$params, &$model, $index, $direction) { + return $index ? array($index .' '. $direction) : null; + } + + function gridDataLimit(&$params, &$model, $start, $limit) { + return $start . ', ' . $limit; + } + + + /************************************************************************** + ************************************************************************** + * gridData POST PROCESSING + */ + + function gridDataPostProcess(&$params, &$model, &$records) { + // Add grid IDs to each record + $this->gridDataPostProcessGridIDs($params, $model, $records); + + // Add the calculated fields (if any), to the model fields + $this->gridDataPostProcessCalculatedFields($params, $model, $records); + + // Perform any requested subtotaling of fields + $this->gridDataPostProcessSubtotal($params, $model, $records); + + // Add in any needed hyperlinks + $this->gridDataPostProcessLinks($params, $model, $records, array()); + + // DEBUG PURPOSES ONLY! + //$params['records'] = $records; + } + + function gridDataPostProcessGridIDs(&$params, &$model, &$records) { + $model_alias = $model->alias; + $id = $model->primaryKey; + + foreach ($records AS &$record) { + $record['grid_id'] = $record[$model_alias][$id]; + } + } + + function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) { + $model_alias = $model->alias; + + foreach ($records AS &$record) { + // Add the calculated fields (if any), to the model fields + if (isset($record[0])) { + $record[$model_alias] = $record[0] + $record[$model_alias]; + unset($record[0]); + } + } + } + + function gridDataPostProcessSubtotal(&$params, &$model, &$records) { + $model_alias = $model->alias; + + // REVISIT : 20090722 + // Horrible solution to something that should be done + // in SQL. But, it works for now, so what the heck... + + $subtotals = array(); + foreach ($params['post']['fields'] AS $field) { + if (preg_match('/subtotal-(.*)$/', $field, $matches)) + $subtotals[] = array('field' => $matches[1], + 'name' => $field, + 'amount' => 0); + } + + foreach ($records AS &$record) { + foreach ($subtotals AS &$subtotal) { + $field = $subtotal['field']; + if (preg_match("/\./", $field)) { + list($tbl, $col) = explode(".", $field); + $record['subtotal-'.$tbl][$col] = + ($subtotal['amount'] += $record[$tbl][$col]); + } + else { + $record[$model->alias]['subtotal-'.$field] = + ($subtotal['amount'] += $record[$model->alias][$field]); + } + } + } + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + // Don't create any links if ordered not to. + if (isset($params['post']['nolinks'])) + return; + + App::import('Helper', 'Html'); + + foreach ($links AS $table => $fields) { + $special = array('controller', 'action', 'id'); + $controller = Inflector::pluralize(Inflector::underscore($table)); + $action = 'view'; + $id = 'id'; + extract(array_intersect_key($fields, array_flip($special))); + foreach ($records AS &$record) { + if (!isset($record[$table])) + continue; + + foreach (array_diff_key($fields, array_flip($special)) AS $field) { + if (!isset($record[$table][$id]) || !isset($record[$table][$field])) + continue; + + // DEBUG PURPOSES ONLY! + //$params['linkrecord'][] = compact('table', 'field', 'id', 'controller', 'record'); + $record[$table][$field] = + '' . + $record[$table][$field] . + ''; + } + } + } + } + + + /************************************************************************** + ************************************************************************** + * gridData OUTPUT + */ + + function gridDataOutput(&$params, &$model, &$records, $pagination) { + $this->gridDataOutputHeader($params, $model); + $this->gridDataOutputXMLHeader($params, $model); + $this->gridDataOutputRootNodeBegin($params, $model); + $this->gridDataOutputSummary($params, $model, $pagination); + $this->gridDataOutputRecords($params, $model, $records); + $this->gridDataOutputRootNodeEnd($params, $model); + } + + function gridDataOutputHeader(&$params, &$model) { + if (!$params['debug']) { + header("Content-type: text/xml;charset=utf-8"); + } + } + + function gridDataOutputXMLHeader(&$params, &$model) { + echo "\n"; + } + + function gridDataOutputRootNodeBegin(&$params, &$model) { + echo "\n"; + } + + function gridDataOutputRootNodeEnd(&$params, &$model) { + echo "\n"; + } + + function gridDataOutputSummary(&$params, &$model, $pagination) { + echo " \n"; + echo " {$pagination['page']}\n"; + echo " {$pagination['total']}\n"; + echo " {$pagination['record_count']}\n"; + + if (isset($params['userdata'])) { + foreach ($params['userdata'] AS $field => $value) + echo ' ' . "{$value}\n"; + } + } + + function gridDataOutputRecords(&$params, &$model, &$records) { + $id_field = 'grid_id'; + foreach ($records AS $record) { + $this->gridDataOutputRecord($params, $model, $record, + $record[$id_field], $params['post']['fields']); + } + } + + function gridDataOutputRecord(&$params, &$model, &$record, $id, $fields) { + echo " \n"; + foreach ($fields AS $field) { + $this->gridDataOutputRecordField($params, $model, $record, $field); + } + echo " \n"; + } + + function gridDataOutputRecordField(&$params, &$model, &$record, $field) { + if (preg_match("/\./", $field)) { + list($tbl, $col) = explode(".", $field); + //pr(compact('record', 'field', 'tbl', 'col')); + $data = $record[$tbl][$col]; + } + else { + $data = $record[$model->alias][$field]; + } + $this->gridDataOutputRecordCell($params, $model, $record, $field, $data); + } + + function gridDataOutputRecordCell(&$params, &$model, &$record, $field, $data) { + // be sure to put text data in CDATA + if (preg_match("/^\d*$/", $data)) + echo " $data\n"; + else + echo " \n"; + } + + function INTERNAL_ERROR($msg, $depth = 0) { + INTERNAL_ERROR($msg, false, $depth+1); + $this->render_empty(); + $this->_stop(); + } + + function render_empty() { + $this->render('/empty'); + } + +} +?> \ No newline at end of file diff --git a/app_helper.php b/app_helper.php new file mode 100644 index 0000000..1b5e0b1 --- /dev/null +++ b/app_helper.php @@ -0,0 +1,50 @@ +params[$mod]) && is_array($url) && !isset($url[$mod])) + $url[$mod] = $this->params[$mod]; + } + return parent::url($url, $full); + } + +} +?> \ No newline at end of file diff --git a/app_model.php b/app_model.php new file mode 100644 index 0000000..7eb7f10 --- /dev/null +++ b/app_model.php @@ -0,0 +1,491 @@ + 5); + + // Function specific log levels + var $function_log_level = array(); + + // Force the module to log at LEAST at this level + var $min_log_level; + + // Force logging of nothing higher than this level + var $max_log_level; + + + // REVISIT : 20090730 + // Why is this constructor crashing? + // Clearly it's in some sort of infinite + // loop, but it seems the correct way + // to have a constructor call the parent... + +/* function __construct() { */ +/* parent::__construct(); */ +/* $this->prClassLevel(5, 'Model'); */ +/* } */ + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: pr + * - Prints out debug information, if the log level allows + */ + + function prClassLevel($level, $class = null) { + $trace = debug_backtrace(false); + $caller = array_shift($trace); + $caller = array_shift($trace); + if (empty($class)) + $class = $caller['class']; + $this->class_log_level[$class] = $level; + } + + function prFunctionLevel($level, $function = null, $class = null) { + $trace = debug_backtrace(false); + $caller = array_shift($trace); + $caller = array_shift($trace); + if (empty($class)) + $class = $caller['class']; + if (empty($function)) + $function = $caller['function']; + $this->function_log_level["{$class}-{$function}"] = $level; + } + + function _pr($level, $mixed, $checkpoint = null) { + if (Configure::read() <= 0) + return; + + $log_level = $this->default_log_level; + + $trace = debug_backtrace(false); + + // Get rid of pr/prEnter/prReturn + $caller = array_shift($trace); + + // The next entry shows where pr was called from, but it + // shows _what_ was called, which is pr/prEntry/prReturn. + $caller = array_shift($trace); + $file = $caller['file']; + $line = $caller['line']; + + // So, this caller holds the calling function name + $caller = $trace[0]; + $function = $caller['function']; + $class = $caller['class']; + //$class = $this->name; + + // Use class or function specific log level if available + if (isset($this->class_log_level[$class])) + $log_level = $this->class_log_level[$class]; + if (isset($this->function_log_level["{$class}-{$function}"])) + $log_level = $this->function_log_level["{$class}-{$function}"]; + + // Establish log level minimums + $min_log_level = $this->min_log_level; + if (is_array($this->min_log_level)) { + $min_show_level = $min_log_level['show']; + $min_log_level = $min_log_level['log']; + } + + // Establish log level maximums + $max_log_level = $this->max_log_level; + if (is_array($this->max_log_level)) { + $max_show_level = $max_log_level['show']; + $max_log_level = $max_log_level['log']; + } + + // Determine the applicable log and show levels + if (is_array($log_level)) { + $show_level = $log_level['show']; + $log_level = $log_level['log']; + } + + // Adjust log level up/down to min/max + if (isset($min_log_level)) + $log_level = max($log_level, $min_log_level); + if (isset($max_log_level)) + $log_level = min($log_level, $max_log_level); + + // Adjust show level up/down to min/max + if (isset($min_show_level)) + $show_level = max($show_level, $min_show_level); + if (isset($max_show_level)) + $show_level = min($show_level, $max_show_level); + + // If the level is insufficient, bail out + if ($level > $log_level) + return; + + if (!empty($checkpoint)) { + $chk = array("checkpoint" => $checkpoint); + if (is_array($mixed)) + $mixed = $chk + $mixed; + else + $mixed = $chk + array($mixed); + } + + static $pr_unique_number = 0; + $pr_id = 'pr-section-class-' . $class . '-print-' . (++$pr_unique_number); + $pr_trace_id = $pr_id . '-trace'; + $pr_output_id = $pr_id . '-output'; + + $pr_entire_base_class = "pr-section"; + $pr_entire_class_class = $pr_entire_base_class . '-class-' . $class; + $pr_entire_function_class = $pr_entire_class_class . '-function-' . $function; + $pr_entire_class = "$pr_entire_base_class $pr_entire_class_class $pr_entire_function_class"; + $pr_header_class = "pr-caller"; + $pr_trace_class = "pr-trace"; + $pr_output_base_class = 'pr-output'; + $pr_output_class_class = $pr_output_base_class . '-class-' . $class; + $pr_output_function_class = $pr_output_class_class . '-function-' . $function; + $pr_output_class = "$pr_output_base_class $pr_output_class_class $pr_output_function_class"; + + echo '
'."\n"; + echo '
'."\n"; + echo '' . "\n"; // End pr_trace_class + $file = str_replace(ROOT.DS, '', $file); + $file = str_replace(CAKE_CORE_INCLUDE_PATH.DS, '', $file); + + echo "$file:$line ($class::$function)" . ";\n"; +/* $log_show_level = isset($show_level) ? $show_level : '?'; */ +/* echo ' L' . $level . "({$log_level}/{$log_show_level})" . ";\n"; */ + echo ' L' . $level . ";\n"; + echo ' stack'.";\n"; + + echo " this "; + echo 't'."/"; + echo 'n'.";\n"; + + echo " $class "; + echo 's'."/"; + echo 'h'."/"; + echo 'n'.";\n"; + + echo " $function "; + echo 's'."/"; + echo 'h'."/"; + echo 'n'.";\n"; + + echo " all "; + echo 's'."/"; + echo 'h'."/"; + echo 'n'."\n"; + + echo '
' . "\n"; // End pr_header_class + + if (isset($show_level) && $level > $show_level) + $display = 'none'; + else + $display = 'block'; + + echo '
'."\n"; + pr($mixed, false, false); + echo '
' . "\n"; // End pr_output_class + echo '
' . "\n"; // End pr_entire_class + } + + function pr($level, $mixed, $checkpoint = null) { + $this->_pr($level, $mixed, $checkpoint); + } + + function prEnter($args, $level = 15) { + $this->_pr($level, $args, 'Function Entry'); + } + + function prReturn($retval, $level = 16) { + $this->_pr($level, $retval, 'Function Return'); + return $retval; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: queryInit + * - Initializes the query fields + */ + function prDump($all = false) { + $vars = get_object_vars($this); + foreach (array_keys($vars) AS $name) { + if (preg_match("/^[A-Z]/", $name)) + unset($vars[$name]); + if (preg_match("/^_/", $name) && !$all) + unset($vars[$name]); + } + pr($vars); + } + + + /** + * Get Enum Values + * Snippet v0.1.3 + * http://cakeforge.org/snippet/detail.php?type=snippet&id=112 + * + * Gets the enum values for MySQL 4 and 5 to use in selectTag() + */ + function getEnumValues($columnName=null, $tableName=null) + { + if ($columnName==null) { return array(); } //no field specified + + if (!isset($tableName)) { + //Get the name of the table + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $tableName = $db->fullTableName($this, false); + } + + //Get the values for the specified column (database and version specific, needs testing) + $result = $this->query("SHOW COLUMNS FROM {$tableName} LIKE '{$columnName}'"); + + //figure out where in the result our Types are (this varies between mysql versions) + $types = null; + if ( isset( $result[0]['COLUMNS']['Type'] ) ) { //MySQL 5 + $types = $result[0]['COLUMNS']['Type']; $default = $result[0]['COLUMNS']['Default']; + } + elseif ( isset( $result[0][0]['Type'] ) ) { //MySQL 4 + $types = $result[0][0]['Type']; $default = $result[0][0]['Default']; + } + else { //types return not accounted for + return array(); + } + + //Get the values + return array_flip(array_merge(array(''), // MySQL sets 0 to be the empty string + explode("','", strtoupper(preg_replace("/(enum)\('(.+?)'\)/","\\2", $types))) + )); + } //end getEnumValues + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: queryInit + * - Initializes the query fields + */ + function queryInit(&$query, $link = true) { + if (!isset($query)) + $query = array(); + if (!isset($query['conditions'])) + $query['conditions'] = array(); + if (!isset($query['group'])) + $query['group'] = null; + if (!isset($query['fields'])) + $query['fields'] = null; + if ($link && !isset($query['link'])) + $query['link'] = array(); + if (!$link && !isset($query['contain'])) + $query['contain'] = array(); + + // In case caller expects query to come back + return $query; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: nameToID + * - Returns the ID of the named item + */ + function nameToID($name) { + $this->cacheQueries = true; + $item = $this->find('first', array + ('recursive' => -1, + 'conditions' => compact('name'), + )); + $this->cacheQueries = false; + if ($item) { + $item = current($item); + return $item['id']; + } + return null; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: statMerge + * - Merges summary data from $b into $a + */ + + function statsMerge (&$a, $b) { + if (!isset($b)) + return; + + if (!isset($a)) { + $a = $b; + } + elseif (!is_array($a) && !is_array($b)) { + $a += $b; + } + elseif (is_array($a) && is_array($b)) { + foreach (array_intersect_key($a, $b) AS $k => $v) + { + if (preg_match("/^sp\./", $k)) + $a[$k] .= '; ' . $b[$k]; + else + $this->statsMerge($a[$k], $b[$k]); + } + $a = array_merge($a, array_diff_key($b, $a)); + } + else { + die ("Can't yet merge array and non-array stats"); + } + } + + + function filter_null($array) { + return array_diff_key($array, array_filter($array, 'is_null')); + } + + function recursive_array_replace($find, $replace, &$data) { + if (!isset($data)) + return; + + if (is_array($data)) { + foreach ($data as $key => &$value) { + $this->recursive_array_replace($find, $replace, $value); + } + return; + } + + if (isset($replace)) + $data = preg_replace($find, $replace, $data); + elseif (preg_match($find, $data)) + $data = null; + } + + function beforeSave() { +/* pr(array('class' => $this->name, */ +/* 'alias' => $this->alias, */ +/* 'function' => 'AppModel::beforeSave')); */ + + // Replace all empty strings with NULL. + // If a particular model doesn't like this, they'll have to + // override the behavior, or set useNullForEmpty to false. + if ($this->useNullForEmpty) + $this->recursive_array_replace("/^\s*$/", null, $this->data); + + if ($this->formatDateFields) { + $alias = $this->alias; + + foreach ($this->_schema AS $field => $info) { + if ($info['type'] == 'date' || $info['type'] == 'timestamp') { + if (isset($this->data[$alias][$field])) { +/* pr("Fix Date for '$alias'.'$field'; current value = " . */ +/* "'{$this->data[$alias][$field]}'"); */ + if ($this->data[$alias][$field] === 'CURRENT_TIMESTAMP') + // Seems CakePHP is broken for the default timestamp. + // It tries to automagically set the value to CURRENT_TIMESTAMP + // which is wholly rejected by MySQL. Just put it back to NULL + // and let the SQL engine deal with the defaults... it's not + // Cake's place to do this anyway :-/ + $this->data[$alias][$field] = null; + else + $this->data[$alias][$field] = + $this->dateFormatBeforeSave($this->data[$alias][$field]); + } + } + } + } + + return true; + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: dateFormatBeforeSave + * - convert dates to database format + */ + + function dateFormatBeforeSave($dateString) { +/* $time = ''; */ +/* if (preg_match('/(\d+(:\d+))/', $dateString, $match)) */ +/* $time = ' '.$match[1]; */ +/* $dateString = preg_replace('/(\d+(:\d+))/', '', $dateString); */ +/* return date('Y-m-d', strtotime($dateString)) . $time; */ + + if (preg_match('/:/', $dateString)) + return date('Y-m-d H:i:s', strtotime($dateString)); + else + return date('Y-m-d', strtotime($dateString)); + } + + function INTERNAL_ERROR($msg, $depth = 0) { + INTERNAL_ERROR($msg, false, $depth+1); + echo $this->requestAction(array('controller' => 'accounts', + 'action' => 'render_empty'), + array('return', 'bare' => false) + ); + $this->_stop(); + } +} diff --git a/config/acl.ini.php b/config/acl.ini.php new file mode 100644 index 0000000..94a9a9a --- /dev/null +++ b/config/acl.ini.php @@ -0,0 +1,74 @@ +; +; SVN FILE: $Id: acl.ini.php 7945 2008-12-19 02:16:01Z gwoo $ +;/** +; * Short description for file. +; * +; * +; * PHP versions 4 and 5 +; * +; * CakePHP(tm) : Rapid Development Framework http://www.cakephp.org/ +; * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) +; * +; * Licensed under The MIT License +; * Redistributions of files must retain the above copyright notice. +; * +; * @filesource +; * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) +; * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project +; * @package cake +; * @subpackage cake.app.config +; * @since CakePHP(tm) v 0.10.0.1076 +; * @version $Revision: 7945 $ +; * @modifiedby $LastChangedBy: gwoo $ +; * @lastmodified $Date: 2008-12-18 18:16:01 -0800 (Thu, 18 Dec 2008) $ +; * @license http://www.opensource.org/licenses/mit-license.php The MIT License +; */ + +; acl.ini.php - Cake ACL Configuration +; --------------------------------------------------------------------- +; Use this file to specify user permissions. +; aco = access control object (something in your application) +; aro = access request object (something requesting access) +; +; User records are added as follows: +; +; [uid] +; groups = group1, group2, group3 +; allow = aco1, aco2, aco3 +; deny = aco4, aco5, aco6 +; +; Group records are added in a similar manner: +; +; [gid] +; allow = aco1, aco2, aco3 +; deny = aco4, aco5, aco6 +; +; The allow, deny, and groups sections are all optional. +; NOTE: groups names *cannot* ever be the same as usernames! +; +; ACL permissions are checked in the following order: +; 1. Check for user denies (and DENY if specified) +; 2. Check for user allows (and ALLOW if specified) +; 3. Gather user's groups +; 4. Check group denies (and DENY if specified) +; 5. Check group allows (and ALLOW if specified) +; 6. If no aro, aco, or group information is found, DENY +; +; --------------------------------------------------------------------- + +;------------------------------------- +;Users +;------------------------------------- + +[username-goes-here] +groups = group1, group2 +deny = aco1, aco2 +allow = aco3, aco4 + +;------------------------------------- +;Groups +;------------------------------------- + +[groupname-goes-here] +deny = aco5, aco6 +allow = aco7, aco8 \ No newline at end of file diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 0000000..06af98a --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,84 @@ +' . "\n"; + echo '

INTERNAL ERROR:

' . "\n"; + echo '

' . $message . '

' . "\n"; + echo '

This error was not caused by anything that you did wrong.' . "\n"; + echo '
It is a problem within the application itself and should be reported to the administrator.

' . "\n"; + + // Print out the entire stack trace + echo '
' . "\nStack Trace:\n"; + echo '
    ' . "\n"; + $trace = array_slice(debug_backtrace(false), $drop); + for ($i = 0; $i < count($trace); ++$i) { + $bline = $trace[$i]['line']; + $bfile = $trace[$i]['file']; + $bfile = str_replace(ROOT.DS, '', $bfile); + $bfile = str_replace(CAKE_CORE_INCLUDE_PATH.DS, '', $bfile); + + if ($i < count($trace)-1) { + $bfunc = $trace[$i+1]['function']; + $bclas = $trace[$i+1]['class']; + } else { + $bfunc = null; + $bclas = null; + } + + echo("
  1. $bfile:$bline (" . ($bclas ? "$bclas::$bfunc" : "entry point") . ")
  2. \n"); + } + echo "
\n"; + + echo '
' . "\nHTTP Request:\n"; + echo '

' . "\n";
+  print_r($_REQUEST);
+  echo "\n
\n"; + + echo ''; + if ($exit) + die(); +} + +/** + * The settings below can be used to set additional paths to models, views and controllers. + * This is related to Ticket #470 (https://trac.cakephp.org/ticket/470) + * + * $modelPaths = array('full path to models', 'second full path to models', 'etc...'); + * $viewPaths = array('this path to views', 'second full path to views', 'etc...'); + * $controllerPaths = array('this path to controllers', 'second full path to controllers', 'etc...'); + * + */ +//EOF +?> \ No newline at end of file diff --git a/config/core.php b/config/core.php new file mode 100644 index 0000000..3add95a --- /dev/null +++ b/config/core.php @@ -0,0 +1,227 @@ + admin_index() and /admin/controller/index + * 'superuser' -> superuser_index() and /superuser/controller/index + */ + //Configure::write('Routing.admin', 'admin'); + +/** + * Turn off all caching application-wide. + * + */ + //Configure::write('Cache.disable', true); +/** + * Enable cache checking. + * + * If set to true, for view caching you must still use the controller + * var $cacheAction inside your controllers to define caching settings. + * You can either set it controller-wide by setting var $cacheAction = true, + * or in each action using $this->cacheAction = true. + * + */ + //Configure::write('Cache.check', true); +/** + * Defines the default error type when using the log() function. Used for + * differentiating error logging and debugging. Currently PHP supports LOG_DEBUG. + */ + define('LOG_ERROR', 2); +/** + * The preferred session handling method. Valid values: + * + * 'php' Uses settings defined in your php.ini. + * 'cake' Saves session files in CakePHP's /tmp directory. + * 'database' Uses CakePHP's database sessions. + * + * To define a custom session handler, save it at /app/config/.php. + * Set the value of 'Session.save' to to utilize it in CakePHP. + * + * To use database sessions, execute the SQL file found at /app/config/sql/sessions.sql. + * + */ + Configure::write('Session.save', 'php'); +/** + * The name of the table used to store CakePHP database sessions. + * + * 'Session.save' must be set to 'database' in order to utilize this constant. + * + * The table name set here should *not* include any table prefix defined elsewhere. + */ + //Configure::write('Session.table', 'cake_sessions'); +/** + * The DATABASE_CONFIG::$var to use for database session handling. + * + * 'Session.save' must be set to 'database' in order to utilize this constant. + */ + //Configure::write('Session.database', 'default'); +/** + * The name of CakePHP's session cookie. + */ + Configure::write('Session.cookie', 'CAKEPHP'); +/** + * Session time out time (in seconds). + * Actual value depends on 'Security.level' setting. + */ + Configure::write('Session.timeout', '120'); +/** + * If set to false, sessions are not automatically started. + */ + Configure::write('Session.start', true); +/** + * When set to false, HTTP_USER_AGENT will not be checked + * in the session + */ + Configure::write('Session.checkAgent', true); +/** + * The level of CakePHP security. The session timeout time defined + * in 'Session.timeout' is multiplied according to the settings here. + * Valid values: + * + * 'high' Session timeout in 'Session.timeout' x 10 + * 'medium' Session timeout in 'Session.timeout' x 100 + * 'low' Session timeout in 'Session.timeout' x 300 + * + * CakePHP session IDs are also regenerated between requests if + * 'Security.level' is set to 'high'. + */ + Configure::write('Security.level', 'high'); +/** + * A random string used in security hashing methods. + */ + Configure::write('Security.salt', 'fbd497077ac32a7ab159333cd7e3eeb85db5c2a5'); +/** + * Compress CSS output by removing comments, whitespace, repeating tags, etc. + * This requires a/var/cache directory to be writable by the web server for caching. + * and /vendors/csspp/csspp.php + * + * To use, prefix the CSS link URL with '/ccss/' instead of '/css/' or use HtmlHelper::css(). + */ + //Configure::write('Asset.filter.css', 'css.php'); +/** + * Plug in your own custom JavaScript compressor by dropping a script in your webroot to handle the + * output, and setting the config below to the name of the script. + * + * To use, prefix your JavaScript link URLs with '/cjs/' instead of '/js/' or use JavaScriptHelper::link(). + */ + //Configure::write('Asset.filter.js', 'custom_javascript_output_filter.php'); +/** + * The classname and database used in CakePHP's + * access control lists. + */ + Configure::write('Acl.classname', 'DbAcl'); + Configure::write('Acl.database', 'default'); +/** + * + * Cache Engine Configuration + * Default settings provided below + * + * File storage engine. + * + * Cache::config('default', array( + * 'engine' => 'File', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'path' => CACHE, //[optional] use system tmp directory - remember to use absolute path + * 'prefix' => 'cake_', //[optional] prefix every cache file with this string + * 'lock' => false, //[optional] use file locking + * 'serialize' => true, [optional] + * )); + * + * + * APC (http://pecl.php.net/package/APC) + * + * Cache::config('default', array( + * 'engine' => 'Apc', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string + * )); + * + * Xcache (http://xcache.lighttpd.net/) + * + * Cache::config('default', array( + * 'engine' => 'Xcache', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string + * 'user' => 'user', //user from xcache.admin.user settings + * 'password' => 'password', //plaintext password (xcache.admin.pass) + * )); + * + * + * Memcache (http://www.danga.com/memcached/) + * + * Cache::config('default', array( + * 'engine' => 'Memcache', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string + * 'servers' => array( + * '127.0.0.1:11211' // localhost, default port 11211 + * ), //[optional] + * 'compress' => false, // [optional] compress data in Memcache (slower, but uses less memory) + * )); + * + */ + Cache::config('default', array('engine' => 'File')); +?> \ No newline at end of file diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..1bccd6c --- /dev/null +++ b/config/database.php @@ -0,0 +1,14 @@ + 'mysql', + 'persistent' => false, + 'host' => 'localhost', + 'login' => 'pmgr', + 'password' => 'pmgruser', + 'database' => 'property_manager', + 'prefix' => 'pmgr_', + ); +} +?> \ No newline at end of file diff --git a/config/database.php.default b/config/database.php.default new file mode 100644 index 0000000..c20fd5d --- /dev/null +++ b/config/database.php.default @@ -0,0 +1,101 @@ + The name of a supported driver; valid options are as follows: + * mysql - MySQL 4 & 5, + * mysqli - MySQL 4 & 5 Improved Interface (PHP5 only), + * sqlite - SQLite (PHP5 only), + * postgres - PostgreSQL 7 and higher, + * mssql - Microsoft SQL Server 2000 and higher, + * db2 - IBM DB2, Cloudscape, and Apache Derby (http://php.net/ibm-db2) + * oracle - Oracle 8 and higher + * firebird - Firebird/Interbase + * sybase - Sybase ASE + * adodb-[drivername] - ADOdb interface wrapper (see below), + * odbc - ODBC DBO driver + * + * You can add custom database drivers (or override existing drivers) by adding the + * appropriate file to app/models/datasources/dbo. Drivers should be named 'dbo_x.php', + * where 'x' is the name of the database. + * + * persistent => true / false + * Determines whether or not the database should use a persistent connection + * + * connect => + * ADOdb set the connect to one of these + * (http://phplens.com/adodb/supported.databases.html) and + * append it '|p' for persistent connection. (mssql|p for example, or just mssql for not persistent) + * For all other databases, this setting is deprecated. + * + * host => + * the host you connect to the database. To add a socket or port number, use 'port' => # + * + * prefix => + * Uses the given prefix for all the tables in this database. This setting can be overridden + * on a per-table basis with the Model::$tablePrefix property. + * + * schema => + * For Postgres and DB2, specifies which schema you would like to use the tables in. Postgres defaults to + * 'public', DB2 defaults to empty. + * + * encoding => + * For MySQL, MySQLi, Postgres and DB2, specifies the character encoding to use when connecting to the + * database. Uses database default. + * + */ +class DATABASE_CONFIG { + + var $default = array( + 'driver' => 'mysql', + 'persistent' => false, + 'host' => 'localhost', + 'login' => 'user', + 'password' => 'password', + 'database' => 'database_name', + 'prefix' => '', + ); + + var $test = array( + 'driver' => 'mysql', + 'persistent' => false, + 'host' => 'localhost', + 'login' => 'user', + 'password' => 'password', + 'database' => 'test_database_name', + 'prefix' => '', + ); +} +?> \ No newline at end of file diff --git a/config/inflections.php b/config/inflections.php new file mode 100644 index 0000000..c0a516e --- /dev/null +++ b/config/inflections.php @@ -0,0 +1,70 @@ + value array of regex used to match words. + * If key matches then the value is returned. + * + * $pluralRules = array('/(s)tatus$/i' => '\1\2tatuses', '/^(ox)$/i' => '\1\2en', '/([m|l])ouse$/i' => '\1ice'); + */ + $pluralRules = array(); +/** + * This is a key only array of plural words that should not be inflected. + * Notice the last comma + * + * $uninflectedPlural = array('.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox'); + */ + $uninflectedPlural = array('.*cash'); +/** + * This is a key => value array of plural irregular words. + * If key matches then the value is returned. + * + * $irregularPlural = array('atlas' => 'atlases', 'beef' => 'beefs', 'brother' => 'brothers') + */ + $irregularPlural = array(); +/** + * This is a key => value array of regex used to match words. + * If key matches then the value is returned. + * + * $singularRules = array('/(s)tatuses$/i' => '\1\2tatus', '/(matr)ices$/i' =>'\1ix','/(vert|ind)ices$/i') + */ + $singularRules = array(); +/** + * This is a key only array of singular words that should not be inflected. + * You should not have to change this value below if you do change it use same format + * as the $uninflectedPlural above. + */ + $uninflectedSingular = $uninflectedPlural; +/** + * This is a key => value array of singular irregular words. + * Most of the time this will be a reverse of the above $irregularPlural array + * You should not have to change this value below if you do change it use same format + * + * $irregularSingular = array('atlases' => 'atlas', 'beefs' => 'beef', 'brothers' => 'brother') + */ + $irregularSingular = array_flip($irregularPlural); +?> \ No newline at end of file diff --git a/config/routes.php b/config/routes.php new file mode 100644 index 0000000..01d09ac --- /dev/null +++ b/config/routes.php @@ -0,0 +1,55 @@ + 'maps', 'action' => 'view', '1'); + +/** + * Here, we are connecting '/' (base path) to our site map. + * It's hardcoded to map #1, but at some point we'll implement + * a login mechanism and the default path will be to log on instead. + */ +Router::connect('/', $default_path); + +/* + * Route for admin functionality + */ +Router::connect('/admin', + array('admin_route' => true) + $default_path); +Router::connect('/admin/:controller/:action/*', + array('admin_route' => true, 'action' => null)); + +/* + * Route for development functionality + */ +Router::connect('/dev', + array('dev_route' => true) + $default_path); +Router::connect('/dev/:controller/:action/*', + array('dev_route' => true, 'action' => null)); + +?> \ No newline at end of file diff --git a/config/sql/db_acl.php b/config/sql/db_acl.php new file mode 100644 index 0000000..5f24eab --- /dev/null +++ b/config/sql/db_acl.php @@ -0,0 +1,79 @@ + array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'model' => array('type'=>'string', 'null' => true), + 'foreign_key' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'alias' => array('type'=>'string', 'null' => true), + 'lft' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'rght' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) + ); + + var $aros = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'model' => array('type'=>'string', 'null' => true), + 'foreign_key' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'alias' => array('type'=>'string', 'null' => true), + 'lft' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'rght' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) + ); + + var $aros_acos = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'aro_id' => array('type'=>'integer', 'null' => false, 'length' => 10, 'key' => 'index'), + 'aco_id' => array('type'=>'integer', 'null' => false, 'length' => 10), + '_create' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + '_read' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + '_update' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + '_delete' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'ARO_ACO_KEY' => array('column' => array('aro_id', 'aco_id'), 'unique' => 1)) + ); + +} +?> \ No newline at end of file diff --git a/config/sql/db_acl.sql b/config/sql/db_acl.sql new file mode 100644 index 0000000..6d507fe --- /dev/null +++ b/config/sql/db_acl.sql @@ -0,0 +1,40 @@ +# $Id: db_acl.sql 7945 2008-12-19 02:16:01Z gwoo $ +# +# Copyright 2005-2008, Cake Software Foundation, Inc. +# +# Licensed under The MIT License +# Redistributions of files must retain the above copyright notice. +# http://www.opensource.org/licenses/mit-license.php The MIT License + +CREATE TABLE acos ( + id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + parent_id INTEGER(10) DEFAULT NULL, + model VARCHAR(255) DEFAULT '', + foreign_key INTEGER(10) UNSIGNED DEFAULT NULL, + alias VARCHAR(255) DEFAULT '', + lft INTEGER(10) DEFAULT NULL, + rght INTEGER(10) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE aros_acos ( + id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + aro_id INTEGER(10) UNSIGNED NOT NULL, + aco_id INTEGER(10) UNSIGNED NOT NULL, + _create CHAR(2) NOT NULL DEFAULT 0, + _read CHAR(2) NOT NULL DEFAULT 0, + _update CHAR(2) NOT NULL DEFAULT 0, + _delete CHAR(2) NOT NULL DEFAULT 0, + PRIMARY KEY(id) +); + +CREATE TABLE aros ( + id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + parent_id INTEGER(10) DEFAULT NULL, + model VARCHAR(255) DEFAULT '', + foreign_key INTEGER(10) UNSIGNED DEFAULT NULL, + alias VARCHAR(255) DEFAULT '', + lft INTEGER(10) DEFAULT NULL, + rght INTEGER(10) DEFAULT NULL, + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/config/sql/i18n.php b/config/sql/i18n.php new file mode 100644 index 0000000..72233ff --- /dev/null +++ b/config/sql/i18n.php @@ -0,0 +1,56 @@ + array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'locale' => array('type'=>'string', 'null' => false, 'length' => 6, 'key' => 'index'), + 'model' => array('type'=>'string', 'null' => false, 'key' => 'index'), + 'foreign_key' => array('type'=>'integer', 'null' => false, 'length' => 10, 'key' => 'index'), + 'field' => array('type'=>'string', 'null' => false, 'key' => 'index'), + 'content' => array('type'=>'text', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'locale' => array('column' => 'locale', 'unique' => 0), 'model' => array('column' => 'model', 'unique' => 0), 'row_id' => array('column' => 'foreign_key', 'unique' => 0), 'field' => array('column' => 'field', 'unique' => 0)) + ); + +} +?> \ No newline at end of file diff --git a/config/sql/i18n.sql b/config/sql/i18n.sql new file mode 100644 index 0000000..484d8a2 --- /dev/null +++ b/config/sql/i18n.sql @@ -0,0 +1,26 @@ +# $Id: i18n.sql 7945 2008-12-19 02:16:01Z gwoo $ +# +# Copyright 2005-2008, Cake Software Foundation, Inc. +# +# Licensed under The MIT License +# Redistributions of files must retain the above copyright notice. +# http://www.opensource.org/licenses/mit-license.php The MIT License + +CREATE TABLE i18n ( + id int(10) NOT NULL auto_increment, + locale varchar(6) NOT NULL, + model varchar(255) NOT NULL, + foreign_key int(10) NOT NULL, + field varchar(255) NOT NULL, + content mediumtext, + PRIMARY KEY (id), +# UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field), +# INDEX I18N_LOCALE_ROW(locale, model, foreign_key), +# INDEX I18N_LOCALE_MODEL(locale, model), +# INDEX I18N_FIELD(model, foreign_key, field), +# INDEX I18N_ROW(model, foreign_key), + INDEX locale (locale), + INDEX model (model), + INDEX row_id (foreign_key), + INDEX field (field) +); \ No newline at end of file diff --git a/config/sql/sessions.php b/config/sql/sessions.php new file mode 100644 index 0000000..7f00a26 --- /dev/null +++ b/config/sql/sessions.php @@ -0,0 +1,53 @@ + array('type'=>'string', 'null' => false, 'key' => 'primary'), + 'data' => array('type'=>'text', 'null' => true, 'default' => NULL), + 'expires' => array('type'=>'integer', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) + ); + +} +?> \ No newline at end of file diff --git a/config/sql/sessions.sql b/config/sql/sessions.sql new file mode 100644 index 0000000..23a1925 --- /dev/null +++ b/config/sql/sessions.sql @@ -0,0 +1,16 @@ +# $Id: sessions.sql 7118 2008-06-04 20:49:29Z gwoo $ +# +# Copyright 2005-2008, Cake Software Foundation, Inc. +# 1785 E. Sahara Avenue, Suite 490-204 +# Las Vegas, Nevada 89104 +# +# Licensed under The MIT License +# Redistributions of files must retain the above copyright notice. +# http://www.opensource.org/licenses/mit-license.php The MIT License + +CREATE TABLE cake_sessions ( + id varchar(255) NOT NULL default '', + data text, + expires int(11) default NULL, + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/controllers/accounts_controller.php b/controllers/accounts_controller.php new file mode 100644 index 0000000..0554f26 --- /dev/null +++ b/controllers/accounts_controller.php @@ -0,0 +1,204 @@ + 'Accounts', 'header' => true), + array('name' => 'All', 'url' => array('controller' => 'accounts', 'action' => 'all')), + array('name' => 'Asset', 'url' => array('controller' => 'accounts', 'action' => 'asset')), + array('name' => 'Liability', 'url' => array('controller' => 'accounts', 'action' => 'liability')), + array('name' => 'Equity', 'url' => array('controller' => 'accounts', 'action' => 'equity')), + array('name' => 'Income', 'url' => array('controller' => 'accounts', 'action' => 'income')), + array('name' => 'Expense', 'url' => array('controller' => 'accounts', 'action' => 'expense')), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * override: sideMenuLinks + * - Generates controller specific links for the side menu + */ + function sideMenuLinks() { + return array_merge(parent::sideMenuLinks(), $this->sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / asset / liability / equity / income / expense / all + * - Generate a chart of accounts + */ + + function index() { $this->all(); } + function asset() { $this->gridView('Asset Accounts'); } + function liability() { $this->gridView('Liability Accounts'); } + function equity() { $this->gridView('Equity Accounts'); } + function income() { $this->gridView('Income Accounts'); } + function expense() { $this->gridView('Expense Accounts'); } + function all() { $this->gridView('All Accounts', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataSetup(&$params) { + parent::gridDataSetup($params); + if (!isset($params['action'])) + $params['action'] = 'all'; + } + + function gridDataCountTables(&$params, &$model) { + // Our count should NOT include anything extra, + // so we need the virtual function to prevent + // the base class from just calling our + // gridDataTables function + return parent::gridDataTables($params, $model); + } + + function gridDataTables(&$params, &$model) { + return array + ('link' => + array(// Models + 'CurrentLedger' => array + (// Models + 'LedgerEntry' + ), + ), + ); + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + return array_merge($fields, + $this->Account->Ledger->LedgerEntry->debitCreditFields(true)); + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + + if (in_array($params['action'], array('asset', 'liability', 'equity', 'income', 'expense'))) { + $conditions[] = array('Account.type' => strtoupper($params['action'])); + } + + // REVISIT : 20090811 + // No security issues have been worked out yet + $conditions[] = array('Account.level >=' => 10); + + return $conditions; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Account'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: newledger + * - Close the current account ledger and create a new one, + * carrying forward any balance if necessary. + */ + + function newledger($id = null) { + $result = $this->Account->closeCurrentLedgers($id); + + if ($result['error']) { + pr(compact('result')); + die("Unable to create new ledger."); + $this->Session->setFlash(__('Unable to create new Ledger.', true)); + } + if ($id) + $this->redirect(array('action'=>'view', $id)); + else + $this->redirect(array('action'=>'index')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: collected + * - Displays the items actually collected for the period + * e.g. How much was collected in rent from 4/1/09 - 5/1/09 + */ + function collected($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + $this->Account->recursive = -1; + $account = $this->Account->read(null, $id); + $account = $account['Account']; + + $accounts = $this->Account->collectableAccounts(); + $payment_accounts = $accounts['all']; + $default_accounts = $accounts['default']; + $this->set(compact('payment_accounts', 'default_accounts')); + + $title = ($account['name'] . ': Collected Report'); + $this->set(compact('account', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific account + */ + + function view($id = null) { + $account = $this->Account->find + ('first', + array('contain' => + array(// Models + 'CurrentLedger' => + array('fields' => array('id', 'sequence', 'name')), + + 'Ledger' => + array('CloseTransaction' => array + ('order' => array('CloseTransaction.stamp' => 'DESC'))), + ), + 'conditions' => array(array('Account.id' => $id), + // REVISIT : 20090811 + // No security issues have been worked out yet + array('Account.level >=' => 10), + ), + ) + ); + + if (empty($account)) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + // Obtain stats across ALL ledgers for the summary infobox + $stats = $this->Account->stats($id, true); + $stats = $stats['Ledger']; + + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + $this->sidemenu_links[] = + array('name' => 'New Ledger', 'url' => array('action' => 'newledger', $id)); + $this->sidemenu_links[] = + array('name' => 'Collected', 'url' => array('action' => 'collected', $id)); + + // Prepare to render + $title = 'Account: ' . $account['Account']['name']; + $this->set(compact('account', 'title', 'stats')); + } + +} diff --git a/controllers/contacts_controller.php b/controllers/contacts_controller.php new file mode 100644 index 0000000..ea2e20d --- /dev/null +++ b/controllers/contacts_controller.php @@ -0,0 +1,202 @@ +sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / all + * - Generate a listing of contacts + */ + + function index() { $this->all(); } + function all() { $this->gridView('All Contacts', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataOrder(&$params, &$model, $index, $direction) { + $order = parent::gridDataOrder($params, $model, $index, $direction); + if ($index === 'Contact.last_name') { + $order[] = 'Contact.first_name ' . $direction; + } + if ($index === 'Contact.first_name') { + $order[] = 'Contact.last_name ' . $direction; + } + return $order; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Contact'] = array('id'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific contact + */ + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + $contact = $this->Contact->find + ('first', array + ('contain' => array + (// Models + 'ContactPhone', + 'ContactEmail', + 'ContactAddress', + 'Customer'), + + 'conditions' => array('Contact.id' => $id), + )); + + // Set up dynamic menu items + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + $this->sidemenu_links[] = + array('name' => 'Edit', + 'url' => array('action' => 'edit', + $id)); + + // Prepare to render. + $title = 'Contact: ' . $contact['Contact']['display_name']; + $this->set(compact('contact', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: edit + */ + + function edit($id = null, $customer_id = null) { + if (isset($this->data)) { + + if (isset($this->params['form']['cancel'])) { + if (isset($this->data['Contact']['id'])) + $this->redirect(array('action'=>'view', $this->data['Contact']['id'])); +/* else */ +/* $this->redirect(array('controller' => 'customers', */ +/* 'action'=>'add', $this->data['Customer']['id'])); */ + return; + } + + // Go through each contact method and strip the bogus ID if new + foreach (array_intersect_key($this->data, + array('ContactPhone'=>1, + 'ContactAddress'=>1, + 'ContactEmail'=>1)) AS $type => $arr) { + foreach ($arr AS $idx => $item) { + if (isset($item['source']) && $item['source'] === 'new') + unset($this->data[$type][$idx]['id']); + } + } + + // Save the contact and all associated data + $this->Contact->saveContact($this->data['Contact']['id'], $this->data); + + // Now that the work is done, let the user view the updated contact + $this->redirect(array('action'=>'view', $this->data['Contact']['id'])); + } + + if ($id) { + $this->data = $this->Contact->find + ('first', array + ('contain' => array + (// Models + 'ContactPhone', + 'ContactEmail', + 'ContactAddress', + 'Customer'), + + 'conditions' => array('Contact.id' => $id), + )); + + $title = 'Contact: ' . $this->data['Contact']['display_name'] . " : Edit"; + } + else { + $title = "Enter New Contact"; + $this->data = array('ContactPhone' => array(), + 'ContactAddress' => array(), + 'ContactEmail' => array()); + } + + $phone_types = array_flip($this->Contact->ContactPhone->getEnumValues('type')); + unset($phone_types[0]); + // REVISIT 20090705 + // Use this to have a mixed case enum + // array_map('ucfirst', array_map('strtolower', $phone_types)) + $phone_types = array_combine($phone_types, $phone_types); + $this->set(compact('phone_types')); + + $method_types = array_flip($this->Contact->getEnumValues + ('type', + $this->Contact->tablePrefix . 'contacts_methods')); + unset($method_types[0]); + $method_types = array_combine($method_types, $method_types); + $this->set(compact('method_types')); + + $method_preferences = array_flip($this->Contact->getEnumValues + ('preference', + $this->Contact->tablePrefix . 'contacts_methods')); + unset($method_preferences[0]); + $method_preferences = array_combine($method_preferences, $method_preferences); + $this->set(compact('method_preferences')); + + $contact_phones = $this->Contact->ContactPhone->phoneList(); + $this->set(compact('contact_phones')); + + $contact_addresses = $this->Contact->ContactAddress->addressList(); + $this->set(compact('contact_addresses')); + + $contact_emails = $this->Contact->ContactEmail->emailList(); + $this->set(compact('contact_emails')); + + // Prepare to render. + //pr($this->data); + $this->set(compact('title')); + $this->render('edit'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: add + * - Adds a new contact + */ + + function add($customer_id = null) { + $this->edit(null, $customer_id); + } + +} diff --git a/controllers/customers_controller.php b/controllers/customers_controller.php new file mode 100644 index 0000000..5244feb --- /dev/null +++ b/controllers/customers_controller.php @@ -0,0 +1,486 @@ + 'Customers', 'header' => true), + array('name' => 'Current', 'url' => array('controller' => 'customers', 'action' => 'current')), + array('name' => 'Past', 'url' => array('controller' => 'customers', 'action' => 'past')), + array('name' => 'All', 'url' => array('controller' => 'customers', 'action' => 'all')), + array('name' => 'Add Customer', 'url' => array('controller' => 'customers', 'action' => 'add')), + ); + + //var $components = array('RequestHandler'); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * override: sideMenuLinks + * - Generates controller specific links for the side menu + */ + function sideMenuLinks() { + return array_merge(parent::sideMenuLinks(), $this->sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / current / past / all + * - Creates a list of customers + */ + + function index() { $this->current(); } + function current() { $this->gridView('Current Tenants', 'current'); } + function past() { $this->gridView('Past Tenants'); } + function all() { $this->gridView('All Customers'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataCountTables(&$params, &$model) { + return array + ('link' => + array(// Models + 'PrimaryContact', + ), + ); + } + + function gridDataTables(&$params, &$model) { + $link = $this->gridDataCountTables($params, $model); + // StatementEntry is needed to determine customer balance + $link['link']['StatementEntry'] = array('fields' => array()); + return $link; + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + return array_merge($fields, + $this->Customer->StatementEntry->chargeDisbursementFields(true)); + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + + if ($params['action'] === 'current') { + $conditions[] = array('Customer.current_lease_count >' => 0); + } + elseif ($params['action'] === 'past') { + $conditions[] = array('Customer.current_lease_count' => 0); + $conditions[] = array('Customer.past_lease_count >' => 0); + } + + return $conditions; + } + + function gridDataOrder(&$params, &$model, $index, $direction) { + $order = array(); + $order[] = parent::gridDataOrder($params, $model, $index, $direction); + + if ($index !== 'PrimaryContact.last_name') + $order[] = parent::gridDataOrder($params, $model, + 'PrimaryContact.last_name', $direction); + if ($index !== 'PrimaryContact.first_name') + $order[] = parent::gridDataOrder($params, $model, + 'PrimaryContact.first_name', $direction); + if ($index !== 'Customer.id') + $order[] = parent::gridDataOrder($params, $model, + 'Customer.id', $direction); + + return $order; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Customer'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: move_in + * - Sets up the move-in page for the given customer. + */ + + function move_in($id = null) { + $customer = array(); + $unit = array(); + + if (isset($id)) { + $this->Customer->recursive = -1; + $customer = current($this->Customer->read(null, $id)); + } + $this->set(compact('customer', 'unit')); + + $title = 'Customer Move-In'; + $this->set(compact('title')); + $this->render('/leases/move'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: move_out + * - prepare to move a customer out of one of their units + */ + + function move_out($id) { + + $customer = $this->Customer->find + ('first', array + ('contain' => array + (// Models + 'Lease' => + array('conditions' => array('Lease.moveout_date' => null), + // Models + 'Unit' => + array('order' => array('sort_order'), + 'fields' => array('id', 'name'), + ), + ), + ), + + 'conditions' => array('Customer.id' => $id), + )); + $this->set('customer', $lease['Customer']); + $this->set('unit', array()); + + $redirect = array('controller' => 'customers', + 'action' => 'view', + $id); + + $title = $customer['Customer']['name'] . ': Prepare Move-Out'; + $this->set(compact('title', 'customer', 'redirect')); + $this->render('/leases/move'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific customer + */ + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + // Get details on this customer, its contacts and leases + $customer = $this->Customer->find + ('first', array + ('contain' => array + (// Models + 'Contact' => + array('order' => array('Contact.display_name'), + // Models + 'ContactPhone', + 'ContactEmail', + 'ContactAddress', + ), + 'Lease' => + array('Unit' => + array('order' => array('sort_order'), + 'fields' => array('id', 'name'), + ), + ), + ), + + 'conditions' => array('Customer.id' => $id), + )); + //pr($customer); + + // Determine how long this customer has been with us. + $leaseinfo = $this->Customer->find + ('first', array + ('link' => array('Lease' => array('fields' => array())), + 'fields' => array('MIN(Lease.movein_date) AS since', + 'IF(Customer.current_lease_count = 0, MAX(Lease.moveout_date), NULL) AS until'), + 'conditions' => array('Customer.id' => $id), + 'group' => 'Customer.id', + )); + $this->set($leaseinfo[0]); + + // Figure out the outstanding balances for this customer + //$this->set('stats', $this->Customer->stats($id)); + $outstanding_balance = $this->Customer->balance($id); + $outstanding_deposit = $this->Customer->securityDepositBalance($id); + + // Figure out if this customer has any non-closed leases + $show_moveout = false; + $show_payment = false; + foreach ($customer['Lease'] AS $lease) { + if (!isset($lease['close_date'])) + $show_payment = true; + if (!isset($lease['moveout_date'])) + $show_moveout = true; + } + + // Set up dynamic menu items + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + $this->sidemenu_links[] = + array('name' => 'Edit', + 'url' => array('action' => 'edit', + $id)); + + $this->sidemenu_links[] = + array('name' => 'Move-In', + 'url' => array('action' => 'move_in', + $id)); + +/* if ($show_moveout) { */ +/* $this->sidemenu_links[] = */ +/* array('name' => 'Move-Out', */ +/* 'url' => array('action' => 'move_out', */ +/* $id)); */ +/* } */ + + if ($show_payment || $outstanding_balance > 0) + $this->sidemenu_links[] = + array('name' => 'New Receipt', + 'url' => array('action' => 'receipt', + $id)); + + if (!$show_moveout && $outstanding_balance > 0) + $this->sidemenu_links[] = + array('name' => 'Write-Off', + 'url' => array('action' => 'bad_debt', + $id)); + + if ($outstanding_balance < 0) + $this->sidemenu_links[] = + array('name' => 'Issue Refund', + 'url' => array('action' => 'refund', $id)); + + // Prepare to render. + $title = 'Customer: ' . $customer['Customer']['name']; + $this->set(compact('customer', 'title', + 'outstanding_balance', + 'outstanding_deposit')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: edit + * - Edit customer information + */ + + function edit($id = null) { + if (isset($this->data)) { + // Check to see if the operation was cancelled. + if (isset($this->params['form']['cancel'])) { + if (isset($this->data['Customer']['id'])) + $this->redirect(array('action'=>'view', $this->data['Customer']['id'])); + + $this->redirect(array('action'=>'index')); + } + + // Make sure we have at least one contact + if (!isset($this->data['Contact']) || count($this->data['Contact']) == 0) { + $this->Session->setFlash("MUST SPECIFY AT LEAST ONE CONTACT", true); + $this->redirect(array('action'=>'view', $this->data['Customer']['id'])); + } + + // Make sure there is a primary contact + if (!isset($this->data['Customer']['primary_contact_entry'])) { + $this->Session->setFlash("MUST SPECIFY A PRIMARY CONTACT", true); + $this->redirect(array('action'=>'view', $this->data['Customer']['id'])); + } + + // Go through each customer and strip the bogus ID if new + foreach ($this->data['Contact'] AS &$contact) { + if (isset($contact['source']) && $contact['source'] === 'new') + unset($contact['id']); + } + + // Save the customer and all associated data + if (!$this->Customer->saveCustomer($this->data['Customer']['id'], + $this->data, + $this->data['Customer']['primary_contact_entry'])) { + $this->Session->setFlash("CUSTOMER SAVE FAILED", true); + pr("CUSTOMER SAVE FAILED"); + } + + // If existing customer, then view it. + if ($this->data['Customer']['id']) + $this->redirect(array('action'=>'view', $this->Customer->id)); + + // Since this is a new customer, go to the move in screen. + $this->redirect(array('action'=>'move_in', $this->Customer->id)); + } + + if ($id) { + // REVISIT : 20090816 + // This should never need to be done by a controller. + // However, until things stabilize, this gives the + // user a way to update any cached items on the + // customer, by just clicking Edit then Cancel. + $this->Customer->update($id); + + // Get details on this customer, its contacts and leases + $customer = $this->Customer->find + ('first', array + ('contain' => array + (// Models + 'Contact' => + array('order' => array('Contact.display_name'), + // Models + 'ContactPhone', + 'ContactEmail', + 'ContactAddress', + ), + 'Lease' => + array('Unit' => + array('order' => array('sort_order'), + 'fields' => array('id', 'name'), + ), + ), + ), + + 'conditions' => array('Customer.id' => $id), + )); + + $this->data = $customer; + $title = 'Customer: ' . $this->data['Customer']['name'] . " : Edit"; + } + else { + $title = "Enter New Customer"; + $this->data = array('Contact' => array(), 'PrimaryContact' => null); + } + + $contact_types = array_flip($this->Customer->ContactsCustomer->getEnumValues('type')); + unset($contact_types[0]); + $contact_types = array_combine($contact_types, $contact_types); + $this->set(compact('contact_types')); + + $contacts = $this->Customer->Contact->contactList(); + $this->set(compact('contacts')); + + // Prepare to render. + //pr($this->data); + $this->set(compact('title')); + $this->render('edit'); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: add + * - Add a new customer + */ + + function add() { + $this->edit(); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: receipt + * - Sets up the receipt entry page for the given customer. + */ + + function receipt($id = null) { + if (isset($id)) { + $this->Customer->recursive = -1; + $customer = $this->Customer->read(null, $id); + $customer = $customer['Customer']; + } + else { + $customer = null; + } + + $TT = new TenderType(); + $payment_types = $TT->paymentTypes(); + $default_type = $TT->defaultPaymentType(); + $this->set(compact('payment_types', 'default_type')); + + $title = ($customer['name'] . ': Receipt Entry'); + $this->set(compact('customer', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: refund + * - Refunds customer charges + */ + + function refund($id) { + $customer = $this->Customer->find + ('first', array + ('contain' => false, + 'conditions' => array(array('Customer.id' => $id), + ), + )); + if (empty($customer)) { + $this->redirect(array('action'=>'view', $id)); + } + + // Determine the customer balance, bailing if the customer owes money + $balance = $this->Customer->balance($id); + if ($balance >= 0) { + $this->redirect(array('action'=>'view', $id)); + } + + // The refund will be for a positive amount + $balance *= -1; + + // Get the accounts capable of paying the refund + $refundAccounts = $this->Customer->StatementEntry->Account->refundAccounts(); + $defaultAccount = current($refundAccounts); + $this->set(compact('refundAccounts', 'defaultAccount')); + + // Prepare to render + $title = ($customer['Customer']['name'] . ': Refund'); + $this->set(compact('title', 'customer', 'balance')); + $this->render('/transactions/refund'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: bad_debt + * - Sets up the write-off entry page, so that the + * user can write off remaining charges of a customer. + */ + + function bad_debt($id) { + $this->Customer->id = $id; + $customer = $this->Customer->find + ('first', array + ('contain' => false, + )); + + // Make sure we have a valid customer to write off + if (empty($customer)) + $this->redirect(array('action' => 'index')); + + // Get the customer balance + $balance = $this->Customer->balance($id); + + // Prepare to render + $title = ($customer['Customer']['name'] . ': Write Off Bad Debt'); + $this->set(compact('title', 'customer', 'balance')); + $this->render('/transactions/bad_debt'); + } + +} diff --git a/controllers/double_entries_controller.php b/controllers/double_entries_controller.php new file mode 100644 index 0000000..42a61a9 --- /dev/null +++ b/controllers/double_entries_controller.php @@ -0,0 +1,66 @@ +sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific entry + */ + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('controller' => 'accounts', 'action'=>'index')); + } + + // Get the Entry and related fields + $entry = $this->DoubleEntry->find + ('first', + array('contain' => array('DebitEntry', 'CreditEntry'), + 'conditions' => array('DoubleEntry.id' => $id), + )); + + $entry += $this->DoubleEntry->DebitEntry->Transaction->find + ('first', + array('contain' => false, + 'conditions' => array('id' => $entry['DebitEntry']['transaction_id']), + )); + + $entry += $this->DoubleEntry->DebitEntry->find + ('first', + array('contain' => array('Ledger' => array('Account')), + 'conditions' => array('DebitEntry.id' => $entry['DebitEntry']['id']), + )); + $entry['DebitLedger'] = $entry['Ledger']; + unset($entry['Ledger']); + + $entry += $this->DoubleEntry->CreditEntry->find + ('first', + array('contain' => array('Ledger' => array('Account')), + 'conditions' => array('CreditEntry.id' => $entry['CreditEntry']['id']), + )); + $entry['CreditLedger'] = $entry['Ledger']; + unset($entry['Ledger']); + + // Prepare to render. + $title = "Double Ledger Entry #{$entry['DoubleEntry']['id']}"; + $this->set(compact('entry', 'title')); + } + +} diff --git a/controllers/leases_controller.php b/controllers/leases_controller.php new file mode 100644 index 0000000..ed501c7 --- /dev/null +++ b/controllers/leases_controller.php @@ -0,0 +1,524 @@ + 'Leases', 'header' => true), + array('name' => 'Active', 'url' => array('controller' => 'leases', 'action' => 'active')), + array('name' => 'Closed', 'url' => array('controller' => 'leases', 'action' => 'closed')), + array('name' => 'Delinquent', 'url' => array('controller' => 'leases', 'action' => 'delinquent')), + array('name' => 'All', 'url' => array('controller' => 'leases', 'action' => 'all')), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * override: sideMenuLinks + * - Generates controller specific links for the side menu + */ + function sideMenuLinks() { + return array_merge(parent::sideMenuLinks(), $this->sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / active / closed / all + * - Generate a listing of leases + */ + + function index() { $this->all(); } + function active() { $this->gridView('Active Leases'); } + function delinquent() { $this->gridView('Delinquent Leases'); } + function closed() { $this->gridView('Closed Leases'); } + function all() { $this->gridView('All Leases', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataSetup(&$params) { + parent::gridDataSetup($params); + if (!isset($params['action'])) + $params['action'] = 'all'; + } + + function gridDataCountTables(&$params, &$model) { + return array + ('link' => array('Unit' => array('fields' => array('id', 'name')), + 'Customer' => array('fields' => array('id', 'name')))); + } + + function gridDataTables(&$params, &$model) { + $link = $this->gridDataCountTables($params, $model); + $link['link']['StatementEntry'] = array('fields' => array()); + return $link; + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + $fields[] = ("IF(" . $this->Lease->conditionDelinquent() . "," . + " 'DELINQUENT', 'CURRENT') AS 'status'"); + return array_merge($fields, + $this->Lease->StatementEntry->chargeDisbursementFields(true)); + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + + if ($params['action'] === 'active') { + $conditions[] = 'Lease.close_date IS NULL'; + } + elseif ($params['action'] === 'delinquent') { + $conditions[] = $this->Lease->conditionDelinquent(); + } + elseif ($params['action'] === 'closed') { + $conditions[] = 'Lease.close_date IS NOT NULL'; + } + + if (isset($customer_id)) + $conditions[] = array('Lease.customer_id' => $customer_id); + + return $conditions; + } + + function gridDataOrder(&$params, &$model, $index, $direction) { + // Do not sort by number, which is type varchar and + // sorts on an ascii basis. Sort by ID instead. + if ($index === 'Lease.number') + $index = 'Lease.id'; + + // Instead of sorting by name, sort by defined order + if ($index === 'Unit.name') + $index = 'Unit.sort_order'; + + $order = array(); + $order[] = parent::gridDataOrder($params, $model, $index, $direction); + + // If sorting by anything other than id/number + // add sorting by id as a secondary condition. + if ($index !== 'Lease.id' && $index !== 'Lease.number') + $order[] = parent::gridDataOrder($params, $model, + 'Lease.id', $direction); + + return $order; + } + +/* function gridDataPostProcess(&$params, &$model, &$records) { */ +/* foreach ($records AS &$record) { */ +/* $record['Lease']['through_date'] */ +/* = $this->Lease->rentChargeThrough($record['Lease']['id']); */ +/* } */ + +/* parent::gridDataPostProcess($params, $model, $records); */ +/* } */ + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Lease'] = array('number'); + $links['Unit'] = array('name'); + $links['Customer'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: move_in + * - execute a move in on a new lease + */ + + function move_in() { + if (!$this->data) + die("Should have some data"); + + // Handle the move in based on the data given + //pr(array('Move-in data', $this->data)); + foreach (array('deposit', 'rent') AS $currency) { + $this->data['Lease'][$currency] + = str_replace('$', '', $this->data['Lease'][$currency]); + } + + $lid = $this->Lease->moveIn($this->data['Lease']['customer_id'], + $this->data['Lease']['unit_id'], + $this->data['Lease']['deposit'], + $this->data['Lease']['rent'], + $this->data['Lease']['movein_date'], + $this->data['Lease']['comment'] + ); + + // Since this is a new lease, go to the invoice + // screen so we can start assessing charges. + $this->redirect(array('action'=>'invoice', $lid, 'move-in')); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: move_out + * - prepare or execute a move out on a specific lease + */ + + function move_out($id = null) { + if ($this->data) { + // Handle the move out based on the data given + //pr($this->data); + + $this->Lease->moveOut($this->data['Lease']['id'], + 'VACANT', + $this->data['Lease']['moveout_date'] + ); + + $this->redirect($this->data['redirect']); + } + + if (!isset($id)) + die("Oh Nooooo!!"); + + $lease = $this->Lease->find + ('first', array + ('contain' => array + (// Models + 'Unit' => + array('order' => array('sort_order'), + 'fields' => array('id', 'name'), + ), + + 'Customer' => + array('fields' => array('id', 'name'), + ), + ), + + 'conditions' => array(array('Lease.id' => $id), + array('Lease.close_date' => null), + ), + )); + $this->set('customer', $lease['Customer']); + $this->set('unit', $lease['Unit']); + $this->set('lease', $lease['Lease']); + + $redirect = array('controller' => 'leases', + 'action' => 'view', + $id); + + $title = ('Lease #' . $lease['Lease']['number'] . ': ' . + $lease['Unit']['name'] . ': ' . + $lease['Customer']['name'] . ': Prepare Move-Out'); + $this->set(compact('title', 'redirect')); + $this->render('/leases/move'); + } + + +/* /\************************************************************************** */ +/* ************************************************************************** */ +/* ************************************************************************** */ +/* * action: promote_credit */ +/* * - Moves any lease credit up to the customer level, so that */ +/* * it may be used for charges other than those on this lease. */ +/* *\/ */ + +/* function promote_surplus($id) { */ +/* $this->Lease->promoteSurplus($id); */ +/* $this->redirect(array('controller' => 'leases', */ +/* 'action' => 'view', */ +/* $id)); */ +/* } */ + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: refund + * - Provides lease customer with a refund + */ + + function refund($id) { + $lease = $this->Lease->find + ('first', array + ('contain' => array + (// Models + 'Unit' => array('fields' => array('id', 'name')), + 'Customer' => array('fields' => array('id', 'name')), + ), + + 'conditions' => array(array('Lease.id' => $id), + // Make sure lease is not closed... + array('Lease.close_date' => null), + ), + )); + if (empty($lease)) { + $this->redirect(array('action'=>'view', $id)); + } + + // Determine the lease balance, bailing if the customer owes money + $balance = $this->Lease->balance($id); + if ($balance >= 0) { + $this->redirect(array('action'=>'view', $id)); + } + + // The refund will be for a positive amount + $balance *= -1; + + // Get the accounts capable of paying the refund + $refundAccounts = $this->Lease->StatementEntry->Account->refundAccounts(); + $defaultAccount = current($refundAccounts); + $this->set(compact('refundAccounts', 'defaultAccount')); + + // Prepare to render + $title = ('Lease #' . $lease['Lease']['number'] . ': ' . + $lease['Unit']['name'] . ': ' . + $lease['Customer']['name'] . ': Refund'); + $this->set(compact('title', 'lease', 'balance')); + $this->render('/transactions/refund'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: bad_debt + * - Sets up the write-off entry page, so that the + * user can write off remaining charges on a lease. + */ + + function bad_debt($id) { + $this->Lease->id = $id; + $lease = $this->Lease->find + ('first', array + ('contain' => array + (// Models + 'Unit' => array('fields' => array('id', 'name')), + 'Customer' => array('fields' => array('id', 'name')), + ), + )); + + // Make sure we have a valid lease to write off + if (empty($lease)) + $this->redirect(array('action' => 'view', $id)); + + // Get the lease balance + $balance = $this->Lease->balance($id); + + // Prepare to render + $title = ('Lease #' . $lease['Lease']['number'] . ': ' . + $lease['Unit']['name'] . ': ' . + $lease['Customer']['name'] . ': Write Off Bad Debt'); + $this->set(compact('title', 'lease', 'balance')); + $this->render('/transactions/bad_debt'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: close + * - Closes a lease to any further action + */ + + // REVISIT : 20090809 + // While cleaning up the sitelink data, then delete reldep() + function reldep($id) { + $this->Lease->id = $id; + $stamp = $this->Lease->field('moveout_date'); + $this->Lease->releaseSecurityDeposits($id, $stamp); + $this->redirect(array('action'=>'view', $id)); + } + + function close($id) { + // REVISIT : 20090708 + // We should probably seek confirmation first... + if (!$this->Lease->closeable($id)) { + $this->INTERNAL_ERROR("This lease is not ready to close"); + $this->redirect(array('action'=>'view', $id)); + } + + $this->Lease->close($id); + $this->redirect(array('action'=>'view', $id)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: invoice + * - Sets up the invoice entry page for the given customer. + */ + + function invoice($id = null, $type = null) { + + $lease = $this->Lease->find + ('first', array + ('contain' => array + (// Models + 'Unit' => + array('order' => array('sort_order'), + 'fields' => array('id', 'name'), + ), + + 'Customer' => + array('fields' => array('id', 'name'), + ), + ), + + 'conditions' => array(array('Lease.id' => $id), + array('Lease.close_date' => null), + ), + )); + + $A = new Account(); + $charge_accounts = $A->invoiceAccounts(); + $default_account = $A->rentAccountID(); + $rent_account = $A->rentAccountID(); + $security_deposit_account = $A->securityDepositAccountID(); + $this->set(compact('charge_accounts', 'default_account', + 'rent_account', 'security_deposit_account')); + + // REVISIT 20090705: + // Of course, the late charge should come from the late_schedule + $default_late = 10; + $this->set(compact('default_late')); + + if ($type === 'move-in') { + $movein = array(); + $movein['time'] = strtotime($lease['Lease']['movein_date']); + $movein['effective_time'] = strtotime($lease['Lease']['movein_date']); + $movein_date = getdate($movein['effective_time']); + $movein['through_time'] = mktime(0, 0, 0, $movein_date['mon'] + 1, 0, $movein_date['year']); + $days_in_month = idate('d', $movein['through_time']); + $movein['prorated_days'] = $days_in_month - $movein_date['mday'] + 1; + $movein['prorated_rent'] = $lease['Lease']['rent'] * $movein['prorated_days'] / $days_in_month; + $movein['prorated'] = $movein['prorated_days'] != $days_in_month; + $movein['deposit'] = $lease['Lease']['deposit']; + $this->set(compact('movein')); + } + + + $title = ('Lease #' . $lease['Lease']['number'] . ': ' . + $lease['Unit']['name'] . ': ' . + $lease['Customer']['name'] . ': Charge Entry'); + $this->set(compact('title', 'lease', 'charge')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: assess_rent/late + * - Assesses the new monthly rent/late charge, if need be + */ + + function assess_rent($date = null) { + $this->Lease->assessMonthlyRentAll($date); + $this->redirect(array('action'=>'index')); + } + function assess_late($date = null) { + $this->Lease->assessMonthlyLateAll($date); + $this->redirect(array('action'=>'index')); + } + function assess_all($date = null) { + $this->Lease->assessMonthlyRentAll($date); + $this->Lease->assessMonthlyLateAll($date); + $this->redirect(array('action'=>'index')); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific lease + */ + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + // Get details about the lease and its ledgers (no ledger entries yet) + $lease = $this->Lease->find + ('first', + array('contain' => + array(// Models + 'LeaseType(id,name)', + 'Unit(id,name)', + 'Customer(id,name)', + ), + 'fields' => array('Lease.*', $this->Lease->delinquentField()), + 'conditions' => array(array('Lease.id' => $id)), + ) + ); + $lease['Lease'] += $lease[0]; + unset($lease[0]); + + // Figure out the outstanding balances for this lease + $outstanding_balance = $this->Lease->balance($id); + $outstanding_deposit = $this->Lease->securityDepositBalance($id); + + // Set up dynamic menu items. Normally, these will only be present + // on an open lease, but it's possible for a lease to be closed, and + // yet still have an outstanding balance. This can happen if someone + // were to reverse charges, or if a payment should come back NSF. + if (!isset($lease['Lease']['close_date']) || $outstanding_balance > 0) { + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + if (!isset($lease['Lease']['moveout_date'])) + $this->sidemenu_links[] = + array('name' => 'Move-Out', 'url' => array('action' => 'move_out', + $id)); + + if (!isset($lease['Lease']['close_date'])) + $this->sidemenu_links[] = + array('name' => 'New Invoice', 'url' => array('action' => 'invoice', + $id)); + + $this->sidemenu_links[] = + array('name' => 'New Receipt', 'url' => array('controller' => 'customers', + 'action' => 'receipt', + $lease['Customer']['id'])); + +/* if ($outstanding_balance < 0) */ +/* $this->sidemenu_links[] = */ +/* array('name' => 'Transfer Credit to Customer', */ +/* 'url' => array('action' => 'promote_surplus', $id)); */ + + // REVISIT : + // Not allowing refund to be issued from the lease, as + // in fact, we should never have a positive lease balance. + // I'll flag this at the moment, since we might get one + // when a charge is reimbursed; a bug that we'll either + // need to fix, or we'll have to revisit this assumption. + if ($outstanding_balance < 0) + $this->INTERNAL_ERROR("Should not have a customer lease credit."); + +/* if ($outstanding_balance < 0) */ +/* $this->sidemenu_links[] = */ +/* array('name' => 'Issue Refund', */ +/* 'url' => array('action' => 'refund', $id)); */ + + if (isset($lease['Lease']['moveout_date']) && $outstanding_balance > 0) + $this->sidemenu_links[] = + array('name' => 'Write-Off', 'url' => array('action' => 'bad_debt', + $id)); + + if ($this->Lease->closeable($id)) + $this->sidemenu_links[] = + array('name' => 'Close', 'url' => array('action' => 'close', + $id)); + } + + // Prepare to render + $title = 'Lease: #' . $lease['Lease']['id']; + $this->set(compact('lease', 'title', + 'outstanding_deposit', + 'outstanding_balance')); + } +} diff --git a/controllers/ledger_entries_controller.php b/controllers/ledger_entries_controller.php new file mode 100644 index 0000000..d1e7805 --- /dev/null +++ b/controllers/ledger_entries_controller.php @@ -0,0 +1,217 @@ +sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / current / past / all + * - Creates a list of ledger entries + */ + + function index() { $this->gridView('All Ledger Entries'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataTables(&$params, &$model) { + $link = + array(// Models + 'Transaction' => + array('fields' => array('id', 'stamp'), + ), + + 'Ledger' => + array('fields' => array('id', 'sequence'), + 'Account' => + array('fields' => array('id', 'name', 'type'), + ), + ), + + 'Tender' => + array('fields' => array('id', 'name', 'nsf_transaction_id'), + ), + +/* 'DebitEntry', */ +/* 'CreditEntry', */ + ); + + return array('link' => $link); + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + return array_merge($fields, + $this->LedgerEntry->debitCreditFields()); + } + + function gridDataFilterTablesTable(&$params, &$model, $table) { + $table = $this->gridDataFilterTableName($params, $model, $table); + // Account is already part of our standard table set. + // Ensure we don't add it in again as part of filtering. + if ($table == 'Account') + return null; + + // Customer needs to be added beneath Transaction + if ($table == 'Customer') + return 'Transaction'; + + return $table; + } + + function gridDataFilterTablesConfig(&$params, &$model, $table) { + $config = parent::gridDataFilterTablesConfig($params, $model, $table); + + // Customer is special in that its linked in by Transaction + // Therefore, the actual table used for the join is 'Transaction', + // not 'Customer', and so we need to specify Customer here. + if ($table == 'Customer') + $config = array('Customer' => $config); + + return $config; + } + + function gridDataFilterConditionsStatement(&$params, &$model, $table, $key, $value) { + //pr(compact('table', 'key', 'value')); + if ($table == 'Account' && $value['value_present'] && $value['value'] === '-AR-') + $value = $this->LedgerEntry->Ledger->Account->accountReceivableAccountID(); + return parent::gridDataFilterConditionsStatement($params, $model, $table, $key, $value); + } + + + function gridDataOrder(&$params, &$model, $index, $direction) { +/* if ($index === 'balance') */ +/* return ($index .' '. $direction); */ + $order = parent::gridDataOrder($params, $model, $index, $direction); + + if ($index === 'Transaction.stamp') { + $order[] = 'LedgerEntry.id ' . $direction; + } + + return $order; + } + + function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) { + parent::gridDataPostProcessCalculatedFields($params, $model, $records); + foreach ($records AS &$record) { + // REVISIT : 20090730 + // We really need the grid to handle this. We probably need to + // either create a hidden column with the nsf id, or pass back + // a list of nsf items as user data. We can then add an onload + // function to sweep through the nsf items and format them. + // For now... this works. + if (!empty($record['Tender']['nsf_transaction_id'])) + $record['Tender']['name'] = + '' . $record['Tender']['name'] . ''; + } + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['LedgerEntry'] = array('id'); + $links['Transaction'] = array('id'); + $links['Ledger'] = array('id'); + $links['Account'] = array('controller' => 'accounts', 'name'); + $links['Tender'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific entry + */ + + function view($id = null) { + $entry = $this->LedgerEntry->find + ('first', + array('contain' => array + ( + 'Transaction' => + array('fields' => array('id', 'stamp'), + ), + + 'Ledger' => + array('fields' => array('id', 'sequence', 'name'), + 'Account' => + array('fields' => array('id', 'name', 'type'), + 'conditions' => + // REVISIT : 20090811 + // No security issues have been worked out yet + array('Account.level >=' => 5), + ), + ), + + 'Tender' => + array('fields' => array('id', 'name'), + ), + + 'DebitDoubleEntry' => array('id'), + 'CreditDoubleEntry' => array('id'), + + 'DebitEntry' => array('fields' => array('id', 'crdr')), + 'CreditEntry' => array('fields' => array('id', 'crdr')), + ), + + 'conditions' => array('LedgerEntry.id' => $id), + )); + + if (empty($entry) || empty($entry['Ledger']['Account'])) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('controller' => 'accounts', 'action'=>'index')); + } + + if (!empty($entry['DebitEntry']) && !empty($entry['CreditEntry'])) + die("LedgerEntry has both a matching DebitEntry and CreditEntry"); + if (empty($entry['DebitEntry']) && empty($entry['CreditEntry'])) + die("LedgerEntry has neither a matching DebitEntry nor a CreditEntry"); + if (empty($entry['DebitEntry']) && count($entry['CreditEntry']) != 1) + die("LedgerEntry has more than one CreditEntry"); + if (empty($entry['CreditEntry']) && count($entry['DebitEntry']) != 1) + die("LedgerEntry has more than one DebitEntry"); + + if (empty($entry['DebitEntry'])) + $entry['MatchingEntry'] = $entry['CreditEntry'][0]; + else + $entry['MatchingEntry'] = $entry['DebitEntry'][0]; + + if (empty($entry['DebitDoubleEntry']['id'])) + $entry['DoubleEntry'] = $entry['CreditDoubleEntry']; + else + $entry['DoubleEntry'] = $entry['DebitDoubleEntry']; + + // REVISIT : 20090816 + // This page doesn't seem very useful, let's just keep it + // all to the double entry view. + $this->redirect(array('controller' => 'double_entries', + 'action' => 'view', + $entry['DoubleEntry']['id'])); + + // Prepare to render. + $title = "Ledger Entry #{$entry['LedgerEntry']['id']}"; + $this->set(compact('entry', 'title')); + } + +} diff --git a/controllers/ledgers_controller.php b/controllers/ledgers_controller.php new file mode 100644 index 0000000..5aa073b --- /dev/null +++ b/controllers/ledgers_controller.php @@ -0,0 +1,149 @@ + 'Ledgers', 'header' => true), + array('name' => 'Current', 'url' => array('controller' => 'ledgers', 'action' => 'current')), + array('name' => 'Closed', 'url' => array('controller' => 'ledgers', 'action' => 'closed')), + array('name' => 'All', 'url' => array('controller' => 'ledgers', 'action' => 'all')), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * override: sideMenuLinks + * - Generates controller specific links for the side menu + */ + function sideMenuLinks() { + return array_merge(parent::sideMenuLinks(), $this->sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / current / closed / all + * - Generate a list of ledgers + */ + + function index() { $this->all(); } + function current() { $this->gridView('Current Ledgers'); } + function closed() { $this->gridView('Closed Ledgers'); } + function all() { $this->gridView('All Ledgers', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataSetup(&$params) { + parent::gridDataSetup($params); + if (!isset($params['action'])) + $params['action'] = 'all'; + } + + function gridDataCountTables(&$params, &$model) { + return array + ('link' => + array(// Models + 'Account', + ), + ); + } + + function gridDataTables(&$params, &$model) { + $tables = $this->gridDataCountTables($params, $model); + $tables['link'][] = 'LedgerEntry'; + $tables['link'][] = 'CloseTransaction'; + return $tables; + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + $fields[] = 'CONCAT(Account.id, "-", Ledger.sequence) AS id_sequence'; + return array_merge($fields, + $this->Ledger->LedgerEntry->debitCreditFields(true)); + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + + if ($params['action'] === 'current') { + $conditions[] = array('Ledger.close_transaction_id' => null); + } + elseif ($params['action'] === 'closed') { + $conditions[] = array('Ledger.close_transaction_id !=' => null); + } + + // REVISIT : 20090811 + // No security issues have been worked out yet + $conditions[] = array('Account.level >=' => 10); + + return $conditions; + } + + function gridDataOrder(&$params, &$model, $index, $direction) { + $id_sequence = false; + if ($index === 'id_sequence') { + $id_sequence = true; + $index = 'Ledger.account_id'; + } + + $order = parent::gridDataOrder($params, $model, $index, $direction); + + if ($id_sequence) { + $order[] = 'Ledger.sequence ' . $direction; + } + + return $order; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Ledger'] = array('id_sequence'); + $links['Account'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific ledger + */ + + function view($id = null) { + $ledger = $this->Ledger->find + ('first', + array('contain' => + array(// Models + 'Account', + ), + 'conditions' => array(array('Ledger.id' => $id), + // REVISIT : 20090811 + // No security issues have been worked out yet + array('Account.level >=' => 10), + ), + ) + ); + + if (empty($ledger)) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + // Get ledger stats for our summary box + $stats = $this->Ledger->stats($id); + + // OK, set our view variables and render! + $title = 'Ledger: #' . $ledger['Account']['id'] .'-'. $ledger['Ledger']['sequence']; + $this->set(compact('ledger', 'title', 'stats')); + } +} diff --git a/controllers/maps_controller.php b/controllers/maps_controller.php new file mode 100644 index 0000000..4a073ad --- /dev/null +++ b/controllers/maps_controller.php @@ -0,0 +1,306 @@ + 20090528: + * We'll need to present only those site area maps that correspond + * to the users particular site. + */ + + function index() { $this->all(); } + function all() { $this->gridView('All Maps', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataTables(&$params, &$model) { + return array + ('link' => array('SiteArea' => array('fields' => array('SiteArea.id', 'SiteArea.name')), + ), + ); + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Map'] = array('id'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Generates a site map page + */ + + function view($id = null, $requested_width = 800) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + $this->set('info', $this->mapInfo($id, $requested_width)); + $this->set('title', "Site Map"); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: map + * - Produces a PNG site map image + */ + + function map($id = null, $requested_width = 800) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + $this->image($this->mapInfo($id, $requested_width)); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * mapInfo + */ + + function mapInfo($id, $requested_width) { + // Set up array to hold the map information + $info = array('map_id' => $id, + 'border' => true, + 'units' => array()); + + // Find all of the map/unit information from this SiteArea + $map = $this->Map->find('first', array('contain' => false, + 'conditions' => array('id' => $id))); + + $units = $this->Map->Unit->find + ('all', + array('link' => + array('Map' => + array('fields' => array()), + + 'CurrentLease' => + array('fields' => array('id', 'paid_through_date', + $this->Map->Unit->CurrentLease-> + delinquentField('CurrentLease')), + 'Customer'), + + 'UnitSize' => + array('fields' => array('id', 'depth', 'width', + 'MapsUnit.pt_top', + 'MapsUnit.pt_left', + 'MapsUnit.transpose')), + ), + 'fields' => array('id', 'name', 'status'), + 'conditions' => array('Map.id' => $id), + )); + +/* pr(compact('map', 'units')); */ +/* $this->render('/empty'); */ +/* return; */ + + /***** + * The preference would be to leave all things "screen" related + * to reside in the view. However, two separate views need this + * information. The 'view' needs it to include a clickable map + * that corresponds to the map image, and of course, the 'map' + * (or 'image') view needs it to render the image. So, in the + * controller for now, unless I come up with a better idea. + *****/ + + // Get the overall site limits, and then compute the + // actual boundary extents, adjusting for a border + $boundary_adjustment = 12; + $bottom = 2*$boundary_adjustment + $map['Map']['depth']; + $right = 2*$boundary_adjustment + $map['Map']['width']; + + // Scale things according to desired display width + $screen_adjustment_factor = $requested_width / $right; + + // Define the overall canvas size + $info['width'] = $right * $screen_adjustment_factor; + $info['depth'] = $bottom * $screen_adjustment_factor; + + // Go through each unit in the map, calculating the map location + foreach ($units AS $unit) { + $lft = $unit['MapsUnit']['pt_left'] + $boundary_adjustment; + $top = $unit['MapsUnit']['pt_top'] + $boundary_adjustment; + + $width = + $unit['MapsUnit']['transpose'] + ? $unit['UnitSize']['depth'] + : $unit['UnitSize']['width']; + + $depth = + $unit['MapsUnit']['transpose'] + ? $unit['UnitSize']['width'] + : $unit['UnitSize']['depth']; + + $lft *= $screen_adjustment_factor; + $top *= $screen_adjustment_factor; + $width *= $screen_adjustment_factor; + $depth *= $screen_adjustment_factor; + + $info['units'][] = + array( 'id' => $unit['Unit']['id'], + 'name' => $unit['Unit']['name'], + 'left' => $lft, + 'right' => $lft + $width, + 'top' => $top, + 'bottom' => $top + $depth, + 'width' => $width, + 'depth' => $depth, + 'n-s' => $unit['MapsUnit']['transpose'] ? 0 : 1, + 'status' => (($unit['Unit']['status'] === 'OCCUPIED' && + !empty($unit[0]['delinquent'])) + ? 'LATE' : $unit['Unit']['status']), + 'data' => $unit, + ); + } + +/* pr($info); */ +/* $this->render('/empty'); exit(); */ + return $info; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: legend + * - Produces a PNG color legend image + */ + + function legend($id = null, $requested_width = 400) { + $status = array_keys($this->Map->Unit->activeStatusEnums()); + $occupied_key = array_search('OCCUPIED', $status); + array_splice($status, $occupied_key+1, 0, array('LATE')); + + $rows = 2; + $cols = (int)((count($status) + $rows - 1) / $rows); + + $info = array('units' => array()); + + // Get the overall site limits, and then compute the + // actual boundary extents, adjusting for a border + $boundary_adjustment = 1; + $item_width = 40; // Absolute values are irrelevant, as they + $item_depth = 10; // will be scaled in the end anyway. + $bottom = 2*$boundary_adjustment + $rows*$item_depth; + $right = 2*$boundary_adjustment + $cols*$item_width; + + // Scale things according to desired display width + $screen_adjustment_factor = $requested_width / $right; + + // Define the overall canvas size + $info['width'] = $right * $screen_adjustment_factor; + $info['depth'] = $bottom * $screen_adjustment_factor; + + // Get a starting point for our top left position. + $top = $lft = $boundary_adjustment; + + // Scale it appropriately. + $top *= $screen_adjustment_factor; + $lft *= $screen_adjustment_factor; + $item_width *= $screen_adjustment_factor; + $item_depth *= $screen_adjustment_factor; + + foreach ($status AS $code) { + $info['units'][] = array('name' => $code, + 'status' => $code, + 'width' => $item_width, + 'depth' => $item_depth, + 'left' => $lft, + 'right' => $lft + $item_width, + 'top' => $top, + 'bottom' => $top + $item_depth); + $top += $item_depth; + if ($top >= $item_depth * $rows) { + $top = $boundary_adjustment * $screen_adjustment_factor; + $lft += $item_width; + } + } + + $this->image($info, true); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * helper: image + * - used by actions map & legend to set up unit information and + * color palates before rendering the PNG image. + */ + + function image($info, $legend = false) { + $debug = false; + + if (!$debug) { + $this->layout = null; + $this->autoLayout = false; + Configure::write('debug', '0'); + } + + // Define our color palate + // REVISIT : 20090513 + // Get colors from DB option tables + $info['palate']['main']['layout']['bg'] = array('red' => 255, 'green' => 255, 'blue' => 255); + $info['palate']['main']['layout']['border'] = array('red' => 192, 'green' => 192, 'blue' => 192); + $info['palate']['main']['layout']['wall'] = array('red' => 0, 'green' => 0, 'blue' => 0); + $info['palate']['unit']['DELETED']['bg'] = array('red' => 0, 'green' => 0, 'blue' => 0); + $info['palate']['unit']['DAMAGED']['bg'] = array('red' => 192, 'green' => 128, 'blue' => 128); + $info['palate']['unit']['COMPANY']['bg'] = array('red' => 128, 'green' => 192, 'blue' => 128); + $info['palate']['unit']['UNAVAILABLE']['bg'] = array('red' => 128, 'green' => 128, 'blue' => 192); + $info['palate']['unit']['RESERVED']['bg'] = array('red' => 192, 'green' => 192, 'blue' => 128); + $info['palate']['unit']['DIRTY']['bg'] = array('red' => 128, 'green' => 192, 'blue' => 192); + $info['palate']['unit']['VACANT']['bg'] = array('red' => 0, 'green' => 255, 'blue' => 128); + $info['palate']['unit']['OCCUPIED']['bg'] = array('red' => 0, 'green' => 128, 'blue' => 255); + $info['palate']['unit']['LATE']['bg'] = array('red' => 255, 'green' => 192, 'blue' => 192); + $info['palate']['unit']['LOCKED']['bg'] = array('red' => 255, 'green' => 64, 'blue' => 64); + $info['palate']['unit']['LIENED']['bg'] = array('red' => 255, 'green' => 0, 'blue' => 128); + + // Determine text color to go with each background + foreach ($info['palate']['unit'] AS &$code) { + $component = $code['bg']; + $method = 3; + if ($method == 1) { + foreach (array('red', 'green', 'blue') AS $prim) + $component[$prim] = 255 - $component[$prim]; + } elseif ($method == 2) { + foreach (array('red', 'green', 'blue') AS $prim) + $component[$prim] = ($component[$prim]) >= 128 ? 0 : 255; + } elseif ($method == 3) { + $val = (sqrt(pow($component['red'], 2) + + pow($component['green'], 2) + + pow($component['blue'], 2)) >= sqrt(3) * 128) ? 0 : 255; + foreach (array('red', 'green', 'blue') AS $prim) + $component[$prim] = $val; + } + + $code['fg'] = $component; + } + + $this->set(compact('info', 'debug')); + $this->render('image'); + } + + + +} + +?> \ No newline at end of file diff --git a/controllers/statement_entries_controller.php b/controllers/statement_entries_controller.php new file mode 100644 index 0000000..b260699 --- /dev/null +++ b/controllers/statement_entries_controller.php @@ -0,0 +1,321 @@ +sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / current / past / all + * - Creates a list of statement entries + */ + + function index() { $this->gridView('All Statement Entries'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataCountTables(&$params, &$model) { + $link = + array(// Models + 'Transaction' => + array('fields' => array('id', 'stamp'), + ), + + 'Customer' => + array('fields' => array('id', 'name'), + ), + + 'Lease' => + array('fields' => array('id', 'number'), + 'Unit' => + array('fields' => array('id', 'name'), + ), + ), + + 'Account' => + array('fields' => array('id', 'name', 'type'), + ), + ); + + if (!empty($params['post']['custom']['statement_entry_id'])) { + $link['ChargeEntry'] = array(); + $link['DisbursementEntry'] = array(); + } + + return array('link' => $link); + } + + function gridDataTables(&$params, &$model) { + $tables = $this->gridDataCountTables($params, $model); + + if (in_array('applied', $params['post']['fields'])) { + $tables['link'] += + array('ChargeEntry' => array(), + 'DisbursementEntry' => array()); + } + + return $tables; + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + + if (in_array('applied', $params['post']['fields'])) { + $fields[] = ("IF(StatementEntry.type = 'CHARGE'," . + " SUM(COALESCE(DisbursementEntry.amount,0))," . + " SUM(COALESCE(ChargeEntry.amount,0)))" . + " AS 'applied'"); + } + if (in_array('unapplied', $params['post']['fields'])) { + $fields[] = ("StatementEntry.amount - (" . + "IF(StatementEntry.type = 'CHARGE'," . + " SUM(COALESCE(DisbursementEntry.amount,0))," . + " SUM(COALESCE(ChargeEntry.amount,0)))" . + ") AS 'unapplied'"); + } + + $fields = array_merge($fields, + $this->StatementEntry->chargeDisbursementFields()); + + return $fields; + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + extract($params['post']['custom']); + + if (!empty($from_date)) + $conditions[] + = array('Transaction.stamp >=' => + $this->StatementEntry->Transaction->dateFormatBeforeSave($from_date)); + + if (!empty($through_date)) + $conditions[] + = array('Transaction.stamp <=' => + $this->StatementEntry->Transaction->dateFormatBeforeSave($through_date . ' 23:59:59')); + + if (isset($account_id)) + $conditions[] = array('StatementEntry.account_id' => $account_id); + + if (isset($customer_id)) + $conditions[] = array('StatementEntry.customer_id' => $customer_id); + + if (isset($statement_entry_id)) { + $conditions[] = array('OR' => + array(array('ChargeEntry.id' => $statement_entry_id), + array('DisbursementEntry.id' => $statement_entry_id))); + } + + if ($params['action'] === 'unreconciled') { + $query = array('conditions' => $conditions); + $set = $this->StatementEntry->reconciledSet('CHARGE', $query, true); + + $entries = array(); + foreach ($set['entries'] AS $entry) + $entries[] = $entry['StatementEntry']['id']; + + $conditions[] = array('StatementEntry.id' => $entries); + $params['userdata']['balance'] = $set['summary']['balance']; + } + + return $conditions; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['StatementEntry'] = array('id'); + $links['Transaction'] = array('id'); + $links['Account'] = array('name'); + $links['Customer'] = array('name'); + $links['Lease'] = array('number'); + $links['Unit'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + function gridDataOrder(&$params, &$model, $index, $direction) { + $order = parent::gridDataOrder($params, $model, $index, $direction); + + // After sorting by whatever the user wants, add these + // defaults into the sort mechanism. If we're already + // sorting by one of them, it will only be redundant, + // and should cause no harm (possible a longer query?) + $order[] = 'Transaction.stamp ' . $direction; + $order[] = 'StatementEntry.effective_date ' . $direction; + $order[] = 'StatementEntry.id ' . $direction; + + return $order; + } + + function gridDataRecordsExecute(&$params, &$model, $query) { +/* if ($params['action'] === '???') { */ +/* $tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1)); */ +/* $tquery['fields'] = array("IF(StatementEntry.type = 'CHARGE'," . */ +/* " SUM(COALESCE(DisbursementEntry.amount,0))," . */ +/* " SUM(COALESCE(ChargeEntry.amount,0)))" . */ +/* " AS 'applied'", */ + +/* "StatementEntry.amount - (" . */ +/* "IF(StatementEntry.type = 'CHARGE'," . */ +/* " SUM(COALESCE(DisbursementEntry.amount,0))," . */ +/* " SUM(COALESCE(ChargeEntry.amount,0)))" . */ +/* ") AS 'balance'", */ +/* ); */ + +/* //pr(compact('tquery')); */ +/* $total = $model->find('first', $tquery); */ +/* $params['userdata']['total'] = $total[0]['applied']; */ +/* $params['userdata']['balance'] = $total[0]['balance']; */ +/* } */ + if ($params['action'] === 'collected') { + $tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1)); + $tquery['fields'] = array("SUM(COALESCE(StatementEntry.amount,0)) AS 'total'"); + $total = $model->find('first', $tquery); + $params['userdata']['total'] = $total[0]['total']; + } + + return parent::gridDataRecordsExecute($params, $model, $query); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: reverse the ledger entry + */ + + function reverse($id = null) { + if ($this->data) { + //pr($this->data); die(); + + $this->StatementEntry->reverse + ($this->data['StatementEntry']['id'], + $this->data['Transaction']['stamp'], + $this->data['Transaction']['comment']); + + $this->redirect(array('action'=>'view', + $this->data['StatementEntry']['id'])); + $this->INTERNAL_ERROR('SHOULD HAVE REDIRECTED'); + } + + $this->StatementEntry->id = $id; + $entry = $this->StatementEntry->find + ('first', array + ('contain' => array('Customer', 'Transaction', 'Account'), + )); + + if (empty($entry)) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('controller' => 'customers', + 'action'=>'index')); + } + + if (!$this->StatementEntry->reversable($id)) { + $this->Session->setFlash(__('Item not reversable.', true)); + $this->redirect(array('action'=>'view', $id)); + } + + // Prepare to render. + $title = ("Charge #{$entry['StatementEntry']['id']}" . + " : {$entry['StatementEntry']['amount']}" . + " : Reverse"); + $this->set(compact('entry', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: waive the ledger entry + */ + + function waive($id) { + $this->StatementEntry->waive($id); + $this->redirect(array('action'=>'view', $id)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific entry + */ + + function view($id = null) { + $entry = $this->StatementEntry->find + ('first', + array('contain' => array + ('Transaction' => array('fields' => array('id', 'type', 'stamp')), + 'Account' => array('id', 'name', 'type'), + 'Customer' => array('fields' => array('id', 'name')), + 'Lease' => array('fields' => array('id')), + ), + + 'conditions' => array(array('StatementEntry.id' => $id), + // REVISIT : 20090811 + // No security issues have been worked out yet + array('Account.level >=' => 5) + ), + )); + + if (empty($entry)) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('controller' => 'accounts', 'action'=>'index')); + } + + $stats = $this->StatementEntry->stats($id); + + if (in_array(strtoupper($entry['StatementEntry']['type']), $this->StatementEntry->debitTypes())) + $stats = $stats['Charge']; + else + $stats = $stats['Disbursement']; + + + if (strtoupper($entry['StatementEntry']['type']) === 'CHARGE') { + + $reversable = $this->StatementEntry->reversable($id); + + // Set up dynamic menu items + if ($reversable || $stats['balance'] > 0) + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + if ($reversable) + $this->sidemenu_links[] = + array('name' => 'Reverse', + 'url' => array('action' => 'reverse', + $id)); + + if ($stats['balance'] > 0) + $this->sidemenu_links[] = + array('name' => 'Waive Balance', + 'url' => array('action' => 'waive', + $id)); + } + + // Prepare to render. + $title = "Statement Entry #{$entry['StatementEntry']['id']}"; + $this->set(compact('entry', 'title', 'stats')); + } + +} diff --git a/controllers/tenders_controller.php b/controllers/tenders_controller.php new file mode 100644 index 0000000..8e8be18 --- /dev/null +++ b/controllers/tenders_controller.php @@ -0,0 +1,258 @@ +sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / all + * - Generate a listing of Tenders + */ + + function index() { $this->all(); } + function all() { $this->gridView('All Legal Tender', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataTables(&$params, &$model) { + return array + ('link' => + array('TenderType', + 'Customer', + 'LedgerEntry' => + array('Transaction', + ), + ), + ); + } + + function gridDataRecordsExecute(&$params, &$model, $query) { + $tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1)); + $tquery['fields'] = array("SUM(COALESCE(LedgerEntry.amount,0)) AS 'total'"); + $total = $model->find('first', $tquery); + $params['userdata']['total'] = $total[0]['total']; + + return parent::gridDataRecordsExecute($params, $model, $query); + } + + function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) { + parent::gridDataPostProcessCalculatedFields($params, $model, $records); + foreach ($records AS &$record) { + // REVISIT : 20090730 + // We really need the grid to handle this. We probably need to + // either create a hidden column with the nsf id, or pass back + // a list of nsf items as user data. We can then add an onload + // function to sweep through the nsf items and format them. + // For now... this works. + if (!empty($record['Tender']['nsf_transaction_id'])) + $record['Tender']['name'] = + '' . $record['Tender']['name'] . ''; + } + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Tender'] = array('name', 'id'); + $links['Customer'] = array('name'); + $links['TenderType'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: deposit + * - Prepares the books for a bank deposit + */ + + function deposit() { + // Prepare a close page... + $deposit_types = $this->Tender->TenderType->depositTypes( + // Testing... limit to only one type + //array('limit' => 1) + ); + $deposit_accounts = $this->Tender->TenderType->Account->depositAccounts(); + + foreach ($deposit_types AS $type_id => &$type) + $type = array('id' => $type_id, + 'name' => $type, + 'stats' => $this->Tender->TenderType->stats($type_id)); + + //pr(compact('deposit_types', 'deposit_accounts')); + + $title = 'Prepare Deposit'; + $this->set(compact('title', 'deposit_types', 'deposit_accounts')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: nsf + * - Marks a tender as having insufficient funds. + */ + + function nsf($id = null) { + if ($this->data) { + $result = $this->Tender->nsf + ($this->data['Tender']['id'], + $this->data['Transaction']['stamp'], + $this->data['Transaction']['comment']); + $this->redirect(array('controller' => 'tenders', + 'action' => 'view', + $this->data['Tender']['id'])); + } + + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + $this->Tender->id = $id; + $tender = $this->Tender->find + ('first', array + ('contain' => array('Customer', 'LedgerEntry' => array('Transaction')), + )); + + // Prepare to render. + $title = "Tender #{$tender['Tender']['id']} : {$tender['Tender']['name']} : NSF"; + $this->set(compact('tender', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific entry + */ + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('controller' => 'accounts', 'action'=>'index')); + } + + // Get the Tender and related fields + $this->Tender->id = $id; + $tender = $this->Tender->find + ('first', array + ('contain' => array('TenderType', 'Customer', 'LedgerEntry' => array('Transaction')), + )); + + + // Set up dynamic menu items + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + // Watch out for the special "Closing" entries + if (!empty($tender['TenderType']['id'])) + $this->sidemenu_links[] = + array('name' => 'Edit', + 'url' => array('action' => 'edit', + $id)); + + if (!empty($tender['Tender']['deposit_transaction_id']) + && empty($tender['Tender']['nsf_transaction_id']) + // Hard to tell what types of items can come back as NSF. + // For now, assume iff it is a named item, it can be NSF. + // (or if we're in development mode) + && (!empty($tender['TenderType']['data1_name']) || !empty($this->params['dev'])) + ) { + $this->sidemenu_links[] = + array('name' => 'NSF', + 'url' => array('action' => 'nsf', + $id)); + } + + // Prepare to render. + $title = "Tender #{$tender['Tender']['id']} : {$tender['Tender']['name']}"; + $this->set(compact('tender', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: edit + * - Edit tender information + */ + + function edit($id = null) { + if (isset($this->data)) { + // Check to see if the operation was cancelled. + if (isset($this->params['form']['cancel'])) { + if (empty($this->data['Tender']['id'])) + $this->redirect(array('action'=>'index')); + + $this->redirect(array('action'=>'view', $this->data['Tender']['id'])); + } + + // Make sure we have tender data + if (empty($this->data['Tender']) || empty($this->data['Tender']['id'])) + $this->redirect(array('action'=>'index')); + + // Figure out which tender type was chosen + // REVISIT : 20090810; Not ready to change tender type + // $tender_type_id = $this->data['Tender']['tender_type_id']; + $tender_type_id = $this->Tender->field('tender_type_id'); + if (empty($tender_type_id)) + $this->redirect(array('action'=>'view', $this->data['Tender']['id'])); + + // Get data fields from the selected tender type + $this->data['Tender'] += $this->data['type'][$tender_type_id]; + unset($this->data['type']); + + // Save the tender and all associated data + $this->Tender->create(); + $this->Tender->id = $this->data['Tender']['id']; + if (!$this->Tender->save($this->data, false)) { + $this->Session->setFlash("TENDER SAVE FAILED", true); + pr("TENDER SAVE FAILED"); + } + + $this->redirect(array('action'=>'view', $this->Tender->id)); + } + + if ($id) { + $this->data = $this->Tender->findById($id); + } else { + $this->redirect(array('action'=>'index')); + } + + $tender_types = $this->Tender->TenderType->find + ('list', array('order' => array('name'))); + $this->set(compact('tender_types')); + + $types = $this->Tender->TenderType->find('all', array('contain' => false)); + $this->set(compact('types')); + + // Prepare to render. + $title = ('Tender #' . $this->data['Tender']['id'] . + ' : ' . $this->data['Tender']['name'] . + " : Edit"); + $this->set(compact('title')); + } +} diff --git a/controllers/transactions_controller.php b/controllers/transactions_controller.php new file mode 100644 index 0000000..eca9677 --- /dev/null +++ b/controllers/transactions_controller.php @@ -0,0 +1,496 @@ + 'Transactions', 'header' => true), + array('name' => 'All', 'url' => array('controller' => 'transactions', 'action' => 'all')), + array('name' => 'Invoices', 'url' => array('controller' => 'transactions', 'action' => 'invoice')), + array('name' => 'Receipts', 'url' => array('controller' => 'transactions', 'action' => 'receipt')), + array('name' => 'Deposits', 'url' => array('controller' => 'transactions', 'action' => 'deposit')), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * override: sideMenuLinks + * - Generates controller specific links for the side menu + */ + function sideMenuLinks() { + return array_merge(parent::sideMenuLinks(), $this->sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / all + * - Generate a listing of transactions + */ + + function index() { $this->all(); } + function all() { $this->gridView('All Transactions', 'all'); } + function invoice() { $this->gridView('Invoices'); } + function receipt() { $this->gridView('Receipts'); } + function deposit() { + $this->sidemenu_links = array + (array('name' => 'Operations', 'header' => true), + array('name' => 'New Deposit', 'url' => array('controller' => 'tenders', + 'action' => 'deposit'))); + $this->gridView('Deposits'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataCountTables(&$params, &$model) { + return array + ('link' => + array(// Models + 'Account' => array('fields' => array()), + ), + ); + } + + function gridDataTables(&$params, &$model) { + $link = $this->gridDataCountTables($params, $model); + $link['link']['StatementEntry'] = array('fields' => array()); + $link['link']['DepositTender'] = array('fields' => array()); + return $link; + } + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + //$fields[] = 'COUNT(StatementEntry.id) AS entries'; + $fields[] = ("IF(Transaction.type = 'DEPOSIT'," . + " COUNT(DepositTender.id)," . + " COUNT(StatementEntry.id)) AS entries"); + return $fields; + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + + if (in_array($params['action'], array('invoice', 'receipt', 'deposit'))) + $conditions[] = array('Transaction.type' => strtoupper($params['action'])); + + // REVISIT : 20090811 + // No security issues have been worked out yet + $conditions[] = array('Account.level >=' => 5); + + return $conditions; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Transaction'] = array('id', 'action' => ($params['action'] == 'deposit' + ? 'deposit_slip' : 'view')); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: postInvoice + * - handles the creation of a charge invoice + */ + + function postInvoice() { + if (!$this->RequestHandler->isPost()) { + echo('

THIS IS NOT A POST FOR SOME REASON

'); + return; + } + + if (!$this->Transaction->addInvoice($this->data, null, + $this->data['Lease']['id'])) { + $this->Session->setFlash("INVOICE FAILED", true); + // REVISIT 20090706: + // Until we can work out the session problems, + // just die. + die("

INVOICE FAILED

"); + } + + $this->layout = null; + $this->autoLayout = false; + $this->autoRender = false; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: postReceipt + * - handles the creation of a receipt + */ + + function postReceipt() { + if (!$this->RequestHandler->isPost()) { + echo('

THIS IS NOT A POST FOR SOME REASON

'); + return; + } + + foreach($this->data['Entry'] AS &$entry) { + $entry['Tender'] = $entry['type'][$entry['tender_type_id']]; + unset($entry['type']); + unset($entry['tender_type_id']); + } + + if (!$this->Transaction->addReceipt($this->data, + $this->data['Customer']['id'], + (isset($this->data['Lease']['id']) + ? $this->data['Lease']['id'] + : null ))) { + $this->Session->setFlash("RECEIPT FAILED", true); + // REVISIT 20090706: + // Until we can work out the session problems, + // just die. + die("

RECEIPT FAILED

"); + } + + $this->layout = null; + $this->autoLayout = false; + $this->autoRender = false; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: postDeposit + * - handles the creation of a deposit transaction + */ + + function postDeposit() { + if (!$this->RequestHandler->isPost()) { + echo('

THIS IS NOT A POST FOR SOME REASON

'); + return; + } + + //pr($this->data); + + // Go through each type of tender presented to the user + // Determine which are to be deposited, and which are to + // have their corresponding account ledgers closed. + $deposit_tender_ids = array(); + $deposit_type_ids = array(); + $close_type_ids = array(); + foreach ($this->data['TenderType'] AS $type_id => $type) { + $type['items'] = unserialize($type['items']); + if (empty($type['selection']) || + $type['selection'] === 'none' || + ($type['selection'] === 'subset' && count($type['items']) == 0)) + continue; + + // The deposit includes either the whole type, or just certain tenders + if ($type['selection'] === 'all') + $deposit_type_ids[] = $type_id; + else + $deposit_tender_ids = array_merge($deposit_tender_ids, $type['items']); + + // Should we close the ledger for this tender type? + // First, the user would have to request that we do so, + // but additionally, we shouldn't close a ledger unless + // all the tenders are included in this deposit. That + // doesn't guarantee that the ledger has a zero balance, + // but it does carry the balance forward, and a total + // deposit would imply a fresh start, so go for it. + if (!empty($type['close']) && $type['selection'] === 'all') + $close_type_ids[] = $type_id; + } + + // Make sure we actually have something to deposit + if (empty($deposit_type_ids) && empty($deposit_tender_ids)) { + $this->Session->setFlash(__('Nothing to Deposit', true)); + $this->redirect(array('controller' => 'tenders', 'action'=>'deposit')); + } + + // Build up a set of conditions based on user selection + $deposit_conditions = array(); + if (!empty($deposit_type_ids)) + $deposit_conditions[] = array('TenderType.id' => $deposit_type_ids); + if (!empty($deposit_tender_ids)) + $deposit_conditions[] = array('DepositTender.id' => $deposit_tender_ids); + + // Add in confirmation that items have not already been deposited + $deposit_conditions = + array(array('DepositTender.deposit_transaction_id' => null), + array('OR' => $deposit_conditions)); + + // Lookup the items to be deposited + $tenders = $this->Transaction->DepositTender->find + ('all', + array('contain' => array('TenderType', 'LedgerEntry'), + 'conditions' => $deposit_conditions, + )); + + // Build the deposit transaction + $deposit = array('Transaction' => array(), 'Entry' => array()); + foreach ($tenders AS $tender) { + $deposit['Entry'][] = + array('tender_id' => $tender['DepositTender']['id'], + 'account_id' => $tender['LedgerEntry']['account_id'], + 'amount' => $tender['LedgerEntry']['amount'], + ); + } + + //pr(compact('deposit_type_ids', 'deposit_tender_ids', 'close_type_ids', 'deposit_conditions', 'deposit')); + + // OK, perform the deposit and associated accounting + $result = $this->Transaction->addDeposit + ($deposit, $this->data['Deposit']['Account']['id']); + //pr(compact('deposit', 'result')); + + // Close any ledgers necessary + if (!empty($close_type_ids)) { + // Find the accounts associated with the types to close ... + $accounts = $this->Transaction->DepositTender->find + ('all', + array('contain' => array('TenderType.account_id'), + 'conditions' => array(array('TenderType.id' => $close_type_ids)), + )); + + // ... and close them + $this->Transaction->Account->closeCurrentLedgers + (array_map(create_function('$item', 'return $item["TenderType"]["account_id"];'), $accounts)); + } + + // Look out for errors + if ($result['error']) { + $this->Session->setFlash(__('Unable to Create Deposit', true)); + $this->redirect(array('controller' => 'tenders', 'action'=>'deposit')); + } + + // Present the deposit slip to the user + $this->redirect(array('controller' => 'transactions', + 'action' => 'deposit_slip', + $result['transaction_id'])); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: postWriteOff + * - handles the write off of bad debt + */ + + function postWriteOff() { + if (!$this->RequestHandler->isPost()) { + echo('

THIS IS NOT A POST FOR SOME REASON

'); + return; + } + + $data = $this->data; + if (empty($data['Customer']['id'])) + $data['Customer']['id'] = null; + if (empty($data['Lease']['id'])) + $data['Lease']['id'] = null; + + pr(compact('data')); + + if (!$this->Transaction->addWriteOff($data, + $data['Customer']['id'], + $data['Lease']['id'])) { + $this->Session->setFlash("WRITE OFF FAILED", true); + // REVISIT 20090706: + // Until we can work out the session problems, + // just die. + die("

WRITE-OFF FAILED

"); + } + + // Return to viewing the lease/customer + if (empty($data['Lease']['id'])) + $this->redirect(array('controller' => 'customers', + 'action' => 'view', + $data['Customer']['id'])); + else + $this->redirect(array('controller' => 'leases', + 'action' => 'view', + $data['Lease']['id'])); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: postRefund + * - handles issuing a customer refund + */ + + function postRefund() { + if (!$this->RequestHandler->isPost()) { + echo('

THIS IS NOT A POST FOR SOME REASON

'); + return; + } + + $data = $this->data; + if (empty($data['Customer']['id'])) + $data['Customer']['id'] = null; + if (empty($data['Lease']['id'])) + $data['Lease']['id'] = null; + + if (!$this->Transaction->addRefund($data, + $data['Customer']['id'], + $data['Lease']['id'])) { + $this->Session->setFlash("REFUND FAILED", true); + // REVISIT 20090706: + // Until we can work out the session problems, + // just die. + die("

REFUND FAILED

"); + } + + // Return to viewing the lease/customer + if (empty($data['Lease']['id'])) + $this->redirect(array('controller' => 'customers', + 'action' => 'view', + $data['Customer']['id'])); + else + $this->redirect(array('controller' => 'leases', + 'action' => 'view', + $data['Lease']['id'])); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: destroy + * - Deletes a transaction and associated entries + * - !!WARNING!! This should be used with EXTREME caution, as it + * irreversibly destroys the data. It is not for normal use. + */ + + function destroy($id = null) { + $this->Transaction->destroy($id); + //$this->redirect(array('action' => 'index')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific transaction + */ + + function view($id = null) { + $transaction = $this->Transaction->find + ('first', + array('contain' => + array(// Models + 'Account(id,name)', + 'Ledger(id,name)', + 'NsfTender(id,name)', + ), + 'conditions' => array(array('Transaction.id' => $id), + // REVISIT : 20090811 + // No security issues have been worked out yet + array('OR' => + array(array('Account.level >=' => 5), + array('Account.id' => null))), + ), + )); + + // REVISIT : 20090815 + // for debug purposes only (pr output) + $this->Transaction->stats($id); + + if (empty($transaction)) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'index')); + } + + if ($transaction['Transaction']['type'] === 'DEPOSIT' || $this->params['dev']) { + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + if ($transaction['Transaction']['type'] === 'DEPOSIT') + $this->sidemenu_links[] = + array('name' => 'View Slip', 'url' => array('action' => 'deposit_slip', $id)); + if ($this->params['dev']) + $this->sidemenu_links[] = + array('name' => 'Destroy', 'url' => array('action' => 'destroy', $id), + 'confirmMessage' => ("This may leave the database in an unstable state." . + " Do NOT do this unless you know what you're doing." . + " Proceed anyway?")); + } + + // OK, prepare to render. + $title = 'Transaction #' . $transaction['Transaction']['id']; + $this->set(compact('transaction', 'title')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: deposit_slip + * - Special presentation + * Processes the user input and updates the database + */ + + function deposit_slip($id) { + // Build a container for the deposit slip data + $deposit = array('types' => array()); + + $this->id = $id; + $deposit += + $this->Transaction->find('first', array('contain' => false)); + + // Get a summary of all forms of tender in the deposit + $result = $this->Transaction->find + ('all', + array('link' => array('DepositTender' => + array('fields' => array(), + 'TenderType', + 'LedgerEntry' => + array('fields' => array()))), + 'fields' => array(//'TenderType.id', 'TenderType.name', + "COUNT(DepositTender.id) AS 'count'", + "SUM(LedgerEntry.amount) AS 'total'"), + //'conditions' => array(array('DepositTender.deposit_transaction_id' => $id)), + 'conditions' => array(array('Transaction.id' => $id)), + 'group' => 'TenderType.id', + )); + + if (empty($result)) { + die(); + $this->Session->setFlash(__('Invalid Deposit.', true)); + $this->redirect(array('action'=>'deposit')); + } + + // Add the summary to our deposit slip data container + foreach ($result AS $type) { + $deposit['types'][$type['TenderType']['id']] = + $type['TenderType'] + $type[0]; + } + + $deposit_total = 0; + foreach ($deposit['types'] AS $type) + $deposit_total += $type['total']; + + if ($deposit['Transaction']['amount'] != $deposit_total) + $this->INTERNAL_ERROR("Deposit items do not add up to deposit slip total"); + + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + $this->sidemenu_links[] = + array('name' => 'View Transaction', 'url' => array('action' => 'view', $id)); + + $title = 'Deposit Slip'; + $this->set(compact('title', 'deposit')); + $this->render('deposit_slip'); + return; + } + +} diff --git a/controllers/units_controller.php b/controllers/units_controller.php new file mode 100644 index 0000000..50f15a0 --- /dev/null +++ b/controllers/units_controller.php @@ -0,0 +1,336 @@ + 'Units', 'header' => true), + array('name' => 'Occupied', 'url' => array('controller' => 'units', 'action' => 'occupied')), + array('name' => 'Vacant', 'url' => array('controller' => 'units', 'action' => 'vacant')), + array('name' => 'Unavailable', 'url' => array('controller' => 'units', 'action' => 'unavailable')), + array('name' => 'All', 'url' => array('controller' => 'units', 'action' => 'all')), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * override: sideMenuLinks + * - Generates controller specific links for the side menu + */ + function sideMenuLinks() { + return array_merge(parent::sideMenuLinks(), $this->sidemenu_links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: index / unavailable / vacant / occupied / all + * - Generate a listing of units + */ + + function index() { $this->all(); } + function unavailable() { $this->gridView('Unavailable Units'); } + function vacant() { $this->gridView('Vacant Units'); } + function occupied() { $this->gridView('Occupied Units'); } + function all() { $this->gridView('All Units', 'all'); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * virtuals: gridData + * - With the application controller handling the gridData action, + * these virtual functions ensure that the correct data is passed + * to jqGrid. + */ + + function gridDataSetup(&$params) { + parent::gridDataSetup($params); + if (!isset($params['action'])) + $params['action'] = 'all'; + } + + function gridDataCountTables(&$params, &$model) { + return array + ('link' => array('UnitSize' => array('fields' => array('id', 'name')), + 'CurrentLease' => array('fields' => array('id')))); + +/* if ($params['action'] === 'occupied') */ +/* $link['Lease'] = array('fields' => array(), */ +/* // Models */ +/* 'Contact' => array('fields' => array('display_name'), */ +/* //'type' => 'LEFT', */ +/* ), */ +/* ); */ + + } + + function gridDataTables(&$params, &$model) { + $link = $this->gridDataCountTables($params, $model); + $link['link']['CurrentLease']['StatementEntry'] = array('fields' => array()); + return $link; + } + +/* function gridDataTables(&$params, &$model) { */ +/* return array */ +/* ('link' => array('Unit' => array('fields' => array('Unit.id', 'Unit.name')), */ +/* 'Customer' => array('fields' => array('Customer.id', 'Customer.name')))); */ +/* } */ + + function gridDataFields(&$params, &$model) { + $fields = parent::gridDataFields($params, $model); + + return array_merge($fields, + $this->Unit->Lease->StatementEntry->chargeDisbursementFields(true)); + } + + function gridDataConditions(&$params, &$model) { + $conditions = parent::gridDataConditions($params, $model); + + if ($params['action'] === 'unavailable') { + $conditions[] = $this->Unit->conditionUnavailable(); + } + elseif ($params['action'] === 'vacant') { + $conditions[] = $this->Unit->conditionVacant(); + } + elseif ($params['action'] === 'occupied') { + $conditions[] = $this->Unit->conditionOccupied(); + } + elseif ($params['action'] === 'unoccupied') { + $conditions[] = array('NOT' => array($this->Unit->conditionOccupied())); + } + + return $conditions; + } + + function gridDataOrder(&$params, &$model, $index, $direction) { + // Instead of sorting by name, sort by defined order + if ($index === 'Unit.name') + $index = 'Unit.sort_order'; + + $order = array(); + $order[] = parent::gridDataOrder($params, $model, $index, $direction); + + // If sorting by anything other than name (defined order) + // add the sort-order as a secondary condition + if ($index !== 'Unit.name') + $order[] = parent::gridDataOrder($params, $model, + 'Unit.sort_order', $direction); + + return $order; + } + + function gridDataPostProcessLinks(&$params, &$model, &$records, $links) { + $links['Unit'] = array('name'); + $links['UnitSize'] = array('name'); + return parent::gridDataPostProcessLinks($params, $model, $records, $links); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: move_in + * - Sets up the move-in page for the given unit. + */ + + function move_in($id = null) { + $customer = array(); + $unit = array(); + + if (isset($id)) { + $this->Unit->recursive = -1; + $unit = current($this->Unit->read(null, $id)); + } + + $title = 'Unit Move-In'; + $this->set(compact('customer', 'unit', 'title')); + $this->render('/leases/move'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: move_out + * - prepare or execute a move out on a specific lease + */ + + function move_out($id) { + + $unit = $this->Unit->find + ('first', array + ('contain' => array + (// Models + 'CurrentLease' => + array(//'conditions' => array('Lease.moveout_date' => null), + // Models + 'Customer' => + array('fields' => array('id', 'name'), + ), + ), + ), + 'conditions' => array('Unit.id' => $id), + )); + $this->set('customer', $unit['CurrentLease']['Customer']); + $this->set('unit', $unit['Unit']); + $this->set('lease', $unit['CurrentLease']); + + $redirect = array('controller' => 'units', + 'action' => 'view', + $id); + + $title = ('Lease #' . $unit['CurrentLease']['number'] . ': ' . + $unit['Unit']['name'] . ': ' . + $unit['CurrentLease']['Customer']['name'] . ': Prepare Move-Out'); + $this->set(compact('title', 'redirect')); + $this->render('/leases/move'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: view + * - Displays information about a specific unit + */ + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid Item.', true)); + $this->redirect(array('action'=>'')); + } + + $unit = $this->Unit->find + ('first', + array('contain' => + array(// Models + 'UnitSize', + 'Lease' => array('Customer'), + 'CurrentLease' => array('Customer') + ), + 'conditions' => array('Unit.id' => $id), + )); + + // Get the balance on each lease. + foreach ($unit['Lease'] AS &$lease) { + $stats = $this->Unit->Lease->stats($lease['id']); + $lease['balance'] = $stats['balance']; + } + + $outstanding_balance = 0; + $outstanding_deposit = 0; + if (isset($unit['CurrentLease']['id'])) { + // Figure out the outstanding balance of the current lease. + $stats = $this->Unit->stats($id); + $outstanding_balance = + $stats['CurrentLease']['balance']; + + // Figure out the total security deposit for the current lease. + $deposits = $this->Unit->Lease->securityDeposits($unit['CurrentLease']['id']); + $outstanding_deposit = $this->Unit->Lease->securityDepositBalance($unit['CurrentLease']['id']); + } + + // Set up dynamic menu items + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + + $this->sidemenu_links[] = + array('name' => 'Edit', 'url' => array('action' => 'edit', + $id)); + + if (isset($unit['CurrentLease']['id']) && + !isset($unit['CurrentLease']['moveout_date'])) { + $this->sidemenu_links[] = + array('name' => 'Move-Out', 'url' => array('action' => 'move_out', + $id)); + } elseif ($this->Unit->available($unit['Unit']['status'])) { + $this->sidemenu_links[] = + array('name' => 'Move-In', 'url' => array('action' => 'move_in', + $id)); + } else { + // Unit is unavailable (dirty, damaged, reserved, business-use, etc) + } + + if (isset($unit['CurrentLease']['id']) && + !isset($unit['CurrentLease']['close_date'])) { + $this->sidemenu_links[] = + array('name' => 'New Invoice', 'url' => array('controller' => 'leases', + 'action' => 'invoice', + $unit['CurrentLease']['id'])); + $this->sidemenu_links[] = + array('name' => 'New Receipt', 'url' => array('controller' => 'customers', + 'action' => 'receipt', + $unit['CurrentLease']['customer_id'])); + } + + // Prepare to render. + $title = 'Unit ' . $unit['Unit']['name']; + $this->set(compact('unit', 'title', + 'outstanding_balance', + 'outstanding_deposit')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: edit + * - Edit unit information + */ + + function edit($id = null) { + if (isset($this->data)) { + // Check to see if the operation was cancelled. + if (isset($this->params['form']['cancel'])) { + if (empty($this->data['Unit']['id'])) + $this->redirect(array('action'=>'index')); + + $this->redirect(array('action'=>'view', $this->data['Unit']['id'])); + } + + // Make sure we have unit data + if (empty($this->data['Unit'])) + $this->redirect(array('action'=>'index')); + + // Make sure we have a rental rate + if (empty($this->data['Unit']['rent'])) + $this->redirect(array('action'=>'view', $this->data['Unit']['id'])); + + // Save the unit and all associated data + $this->Unit->create(); + $this->Unit->id = $this->data['Unit']['id']; + if (!$this->Unit->save($this->data, false)) { + $this->Session->setFlash("UNIT SAVE FAILED", true); + pr("UNIT SAVE FAILED"); + } + + $this->redirect(array('action'=>'view', $this->Unit->id)); + } + + if ($id) { + $this->data = $this->Unit->findById($id); + $title = 'Unit ' . $this->data['Unit']['name'] . " : Edit"; + } + else { + $title = "Enter New Unit"; + $this->data = array(); + } + + $statusEnums = $this->Unit->allowedStatusSet($id); + $statusEnums = array_combine(array_keys($statusEnums), + array_keys($statusEnums)); + $this->set(compact('statusEnums')); + + $unit_sizes = $this->Unit->UnitSize->find + ('list', array('order' => array('unit_type_id', 'width', 'depth', 'id'))); + $this->set(compact('unit_sizes')); + + // Prepare to render. + pr($this->data); + $this->set(compact('title')); + } + + +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..4e60b84 --- /dev/null +++ b/index.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/models/account.php b/models/account.php new file mode 100644 index 0000000..36a9d2b --- /dev/null +++ b/models/account.php @@ -0,0 +1,389 @@ + array( + 'className' => 'Ledger', + // REVISIT 20090702: + // I would prefer this statement, which has no + // engine specific code. However, it doesn't + // work with the Linkable behavior. I need to + // look into that, just not right now. + //'conditions' => array(array('CurrentLedger.close_transaction_id' => null)), + 'conditions' => array('CurrentLedger.close_transaction_id IS NULL'), + ), + ); + + var $hasMany = array( + 'Ledger', + 'LedgerEntry', + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: type + * - Returns the type of this account + */ + function type($id) { + $this->cacheQueries = true; + $account = $this->find('first', array + ('recursive' => -1, + 'fields' => array('type'), + 'conditions' => array(array('Account.id' => $id)), + )); + $this->cacheQueries = false; + + return $account['Account']['type']; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: fundamentalType + * - Returns the fundmental type of the account, credit or debit + */ + function fundamentalType($id_or_type) { + if (is_numeric($id_or_type)) + $type = $this->type($id_or_type); + else + $type = $id_or_type; + + // Asset and Expense accounts are debit accounts + if (in_array(strtoupper($type), array('ASSET', 'EXPENSE'))) + return 'debit'; + + // Otherwise, it's a credit account + return 'credit'; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: fundamentalOpposite + * - Returns the opposite fundmental type of the account, credit or debit + */ + function fundamentalOpposite($id_or_type) { + if (in_array(strtolower($id_or_type), array('credit', 'debit'))) + $fund = $id_or_type; + else + $fund = $this->fundamentalType($id_or_type); + + if (strtolower($fund) == 'debit') + return 'credit'; + + return 'debit'; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: name + * - Returns the name of this account + */ + function name($id) { + $this->cacheQueries = true; + $account = $this->find('first', array + ('recursive' => -1, + 'fields' => array('name'), + 'conditions' => array(array('Account.id' => $id)), + )); + $this->cacheQueries = false; + + return $account['Account']['name']; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: debitCreditFields + * - Returns the fields necessary to determine whether the queried + * entries are a debit, or a credit, and also the effect each have + * on the overall balance of the account. + */ + + function debitCreditFields($sum = false, $balance = true, + $entry_name = 'LedgerEntry', $account_name = 'Account') { + return $this->LedgerEntry->debitCreditFields + ($sum, $balance, $entry_name, $account_name); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: Account IDs + * - Returns the ID of the desired account + */ + + function lookup($name, $check = true) { + $id = $this->nameToID($name); + if (empty($id) && $check) + $this->INTERNAL_ERROR("Missing Account '$name'"); + return $id; + } + + function securityDepositAccountID() { return $this->lookup('Security Deposit'); } + function rentAccountID() { return $this->lookup('Rent'); } + function lateChargeAccountID() { return $this->lookup('Late Charge'); } + function nsfAccountID() { return $this->lookup('NSF'); } + function nsfChargeAccountID() { return $this->lookup('NSF Charge'); } + function taxAccountID() { return $this->lookup('Tax'); } + function accountReceivableAccountID() { return $this->lookup('A/R'); } + function accountPayableAccountID() { return $this->lookup('A/P'); } + function cashAccountID() { return $this->lookup('Cash'); } + function checkAccountID() { return $this->lookup('Check'); } + function moneyOrderAccountID() { return $this->lookup('Money Order'); } + function achAccountID() { return $this->lookup('ACH'); } + function concessionAccountID() { return $this->lookup('Concession'); } + function waiverAccountID() { return $this->lookup('Waiver'); } + function pettyCashAccountID() { return $this->lookup('Petty Cash'); } + function invoiceAccountID() { return $this->lookup('Invoice'); } + function receiptAccountID() { return $this->lookup('Receipt'); } + function badDebtAccountID() { return $this->lookup('Bad Debt'); } + function customerCreditAccountID() { return $this->lookup( + // REVISIT : 20090816 + // Use of A/R works, and saves an excess of accounts. + // However, a dedicated account is nice, since it can + // quickly be spotted how much is _really_ due, vs + // how much has been pre-paid. Customer credits in + // A/R is not as clear, although a report is an + // obvious solution. + //'A/R' + 'Credit' + ); } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: fundamentalAccounts + * - Returns an array of accounts by their fundamental type + */ + + function fundamentalAccounts($ftype) { + $this->cacheQueries = true; + $account = $this->find('all', array + ('contain' => array('CurrentLedger'), + 'fields' => array('Account.id', 'Account.type', 'Account.name', 'CurrentLedger.id'), + 'conditions' => array('Account.type' => strtoupper($ftype)) + )); + $this->cacheQueries = false; + + return $account; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: relatedAccounts + * - Returns an array of accounts related by similar attributes + */ + + function relatedAccounts($attribute, $extra = null) { + $this->cacheQueries = true; + $accounts = $this->find('all', array + ('contain' => array('CurrentLedger'), + 'fields' => array('Account.id', 'Account.type', 'Account.name', 'CurrentLedger.id'), + 'conditions' => array('Account.'.$attribute => true), + 'order' => array('Account.name'), + ) + (isset($extra) ? $extra : array()) + ); + $this->cacheQueries = false; + + // Rearrange to be of the form (id => name) + $rel_accounts = array(); + foreach ($accounts AS $acct) { + $rel_accounts[$acct['Account']['id']] = $acct['Account']['name']; + } + + return $rel_accounts; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: xxxAccounts + * - Returns an array of accounts suitable for activity xxx + */ + + function invoiceAccounts() { + return $this->relatedAccounts('invoices', array('order' => 'name')); + } + + function receiptAccounts() { + return $this->relatedAccounts('receipts', array('order' => 'name')); + } + + function depositAccounts() { + return $this->relatedAccounts('deposits', array('order' => 'name')); + } + + function refundAccounts() { + return $this->relatedAccounts('refunds', array('order' => 'name')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: collectableAccounts + * - Returns an array of accounts suitable to show income collection + */ + + function collectableAccounts() { + $accounts = $this->receiptAccounts(); + + foreach(array($this->customerCreditAccountID(), + $this->securityDepositAccountID(), + $this->nsfAccountID(), + $this->waiverAccountID(), + $this->badDebtAccountID(), + //$this->lookup('Closing'), + //$this->lookup('Equity'), + ) + AS $account_id) { + $accounts[$account_id] = $this->name($account_id); + } + + $accounts['all'] = $accounts['default'] = $accounts; + + foreach(array($this->concessionAccountID(), + $this->waiverAccountID(), + $this->badDebtAccountID(), + ) + AS $account_id) { + unset($accounts['default'][$account_id]); + } + + return $accounts; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: currentLedgerID + * - Returns the current ledger ID of the account + */ + function currentLedgerID($id) { + $this->cacheQueries = true; + $item = $this->find('first', array + ('contain' => 'CurrentLedger', + 'conditions' => array('Account.id' => $id), + )); + $this->cacheQueries = false; + return $item['CurrentLedger']['id']; + } + + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: ledgers + * - Returns an array of ledger ids from the given account + */ + function ledgers($id, $all = false) { + if ($all) { + $contain = array('Ledger' => array('fields' => array('Ledger.id'))); + } else { + $contain = array('CurrentLedger' => array('fields' => array('CurrentLedger.id'))); + } + + $this->cacheQueries = true; + $account = $this->find('first', array + ('contain' => $contain, + 'fields' => array(), + 'conditions' => array(array('Account.id' => $id)), + )); + $this->cacheQueries = false; + + if ($all) { + $ledger_ids = array(); + foreach ($account['Ledger'] AS $ledger) + array_push($ledger_ids, $ledger['id']); + } + else { + $ledger_ids = array($account['CurrentLedger']['id']); + } + +/* pr(array('function' => 'Account::ledgers', */ +/* 'args' => compact('id', 'all'), */ +/* 'return' => $ledger_ids)); */ + + return $ledger_ids; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: closeCurrentLedger + * - Closes the current account ledger, and opens a new one + * with the old balance carried forward. + */ + function closeCurrentLedgers($ids = null) { + + $this->cacheQueries = true; + $account = $this->find('all', array + ('contain' => array('CurrentLedger.id'), + 'fields' => array(), + 'conditions' => (empty($ids) + ? array() + : array(array('Account.id' => $ids))) + )); + $this->cacheQueries = false; + //pr(compact('id', 'account')); + + $ledger_ids = array(); + foreach ($account AS $acct) + $ledger_ids[] = $acct['CurrentLedger']['id']; + + return $this->Ledger->closeLedgers($ledger_ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: ledgerEntries + * - Returns an array of ledger entries that belong to the given + * account, either just from the current ledger, or from all ledgers. + */ + function ledgerEntries($id, $all = false, $cond = null, $link = null) { + $ledgers = $this->ledgers($id, $all); + return $this->Ledger->ledgerEntries($ledgers, $cond, $link); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested account. + */ + + function stats($id = null, $all = false, $query = null) { + if (!$id) + return null; + + $this->queryInit($query); + $query['link'] = array('Account' => $query['link']); + + $stats = array(); + foreach ($this->ledgers($id, $all) AS $ledger) + $this->statsMerge($stats['Ledger'], + $this->Ledger->stats($ledger, $query)); + + return $stats; + } + +} +?> \ No newline at end of file diff --git a/models/behaviors/linkable.php b/models/behaviors/linkable.php new file mode 100644 index 0000000..2a60128 --- /dev/null +++ b/models/behaviors/linkable.php @@ -0,0 +1,486 @@ +find('all', array('link' => 'User', 'conditions' => 'project_id = 1')) + * - Won't produce the desired result as data came from users table will be lost. + * $User->find('all', array('link' => 'Project', 'conditions' => 'project_id = 1')) + * - Will fetch all users related to the specified project in one query + * + * - On data mining as a much lighter approach - can reduce 300+ query find operations + * in one single query with joins; "or your money back!" ;-) + * + * - Has the 'fields' param enabled to make it easy to replace Containable usage, + * only change the 'contain' param to 'link'. + * + * Linkable Behavior. Taking it easy in your DB. + * RafaelBandeira + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @version 1.0; + */ + + +/********************************************************************** + * NOTE TO 20090615: + * This structure should be linkable (it was my test case). + + $entry = $this->LedgerEntry->find + ('first', + array('link' => array('DebitLedger' => + array( + 'fields' => array('id'), + 'alias' => 'MyDebitLedger', + 'Account' => + array( + 'fields' => array('id'), + 'alias' => 'MyDebitAccount'), + ), + + 'MyCreditLedger' => + array( + 'fields' => array('id'), + 'class' => 'CreditLedger', + 'MyCreditAccount' => + array( + 'fields' => array('id'), + 'class' => 'Account'), + ), + + 'MyOtherLedger' => + array( + 'fields' => array('id'), + 'class' => 'Ledger', + 'alias' => 'AliasToMyOtherLedger', + 'Account' => + array( + 'fields' => array('id'), + 'alias' => 'AliasToMyOtherAccount'), + ), + ), + + 'conditions' => array('LedgerEntry.id' => $id), + )); +**********************************************************************/ + + +class LinkableBehavior extends ModelBehavior { + + protected $_key = 'link'; + + protected $_options = array( + 'type' => true, 'table' => true, 'alias' => true, 'joins' => true, + 'conditions' => true, 'fields' => true, 'reference' => true, + 'class' => true, 'defaults' => true, 'linkalias' => true, + ); + + protected $_defaults = array('type' => 'LEFT'); + + function pr($lev, $mixed) { + if ($lev >= 3) + return; + + pr($mixed); + return; + + $trace = debug_backtrace(false); + //array_shift($trace); + $calls = array(); + foreach ($trace AS $call) { + $call = array_intersect_key($call, + array('file'=>1, + 'line'=>1, + //'class'=>1, + 'function'=>1, + )); + $calls[] = implode("; ", $call); + } + pr(array('debug' => $mixed, 'stack' => $calls)); + } + + /* + * This is a function for made recursive str_replaces in an array + * NOTE: The palacement of this function is terrible, but I don't + * know if I really want to go down this path or not. + */ + function recursive_array_replace($find, $replace, &$data) { + if (!isset($data)) + return; + + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value)) { + $this->recursive_array_replace($find, $replace, $data[$key]); + } else { + $data[$key] = str_replace($find, $replace, $value); + } + } + } else { + $data = str_replace($find, $replace, $data); + } + } + + public function beforeFind(&$Model, $query) { + if (!isset($query[$this->_key])) + return $query; + + if (!isset($query['fields']) || $query['fields'] === true) { + $query['fields'] = $Model->getDataSource()->fields($Model); + } elseif (!is_array($query['fields'])) { + $query['fields'] = array($query['fields']); + } + $query = am(array('joins' => array()), $query, array('recursive' => -1)); + + $reference = array('class' => $Model->alias, + 'alias' => $Model->alias, + ); + + $result = array_diff_key($query, array($this->_key => 1)); + $this->buildQuery($Model, $Model->alias, $Model->alias, $Model->findQueryType, + $query[$this->_key], $result); + return $result; + } + + function buildQuery(&$Reference, $referenceClass, $referenceAlias, $query_type, $links, &$result) { + if (empty($links)) + return; + + $this->pr(10, + array('begin' => 'Linkable::buildQuery', + 'args' => compact('referenceClass', 'referenceAlias', 'query_type', 'links'), + )); + + //$defaults = $this->_defaults;// + array($this->_key => array()); + //$optionsKeys = $this->_options + array($this->_key => true); + + $links = Set::normalize($links); + $this->pr(24, + array('checkpoint' => 'Normalized links', + compact('links'), + )); + foreach ($links as $alias => $options) { + if (is_null($options)) { + $options = array(); + } + //$options = array_intersect_key($options, $optionsKeys); + //$options = am($this->_defaults, compact('alias'), $options); + + $options += compact('alias'); + $options += $this->_defaults; + + if (empty($options['alias'])) { + throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__)); + } + + if (empty($options['class'])) + $options['class'] = $alias; + + if (!isset($options['conditions'])) + $options['conditions'] = null; + elseif (!is_array($options['conditions'])) + $options['conditions'] = array($options['conditions']); + + $this->pr(20, + array('checkpoint' => 'Begin Model Work', + compact('referenceAlias', 'alias', 'options'), + )); + + $modelClass = $options['class']; + $modelAlias = $options['alias']; + $Model =& ClassRegistry::init($modelClass); // the incoming model to be linked in query + + $this->pr(12, + array('checkpoint' => 'Model Established', + 'Reference' => ($referenceAlias .' : '. $referenceClass . + ' ('. $Reference->alias .' : '. $Reference->name .')'), + 'Model' => ($modelAlias .' : '. $modelClass . + ' ('. $Model->alias .' : '. $Model->name .')'), + )); + + $db =& $Model->getDataSource(); + + $associatedThroughReference = 0; + $association = null; + + // Figure out how these two models are related, creating + // a relationship if one doesn't otherwise already exists. + if (($associations = $Reference->getAssociated()) && + isset($associations[$Model->alias])) { + $this->pr(12, array('checkpoint' => "Reference ($referenceClass) defines association to Model ($modelClass)")); + $associatedThroughReference = 1; + $type = $associations[$Model->alias]; + $association = $Reference->{$type}[$Model->alias]; + } + elseif (($associations = $Model->getAssociated()) && + isset($associations[$Reference->alias])) { + $this->pr(12, array('checkpoint' => "Model ($modelClass) defines association to Reference ($referenceClass)")); + $type = $associations[$Reference->alias]; + $association = $Model->{$type}[$Reference->alias]; + } + else { + // No relationship... make our best effort to create one. + $this->pr(12, array('checkpoint' => "No assocation between Reference ($referenceClass) and Model ($modelClass)")); + $type = 'belongsTo'; + $Model->bind($Reference->alias); + // Grab the association now, since we'll unbind in a moment. + $association = $Model->{$type}[$Reference->alias]; + $Model->unbindModel(array('belongsTo' => array($Reference->alias))); + } + + // Determine which model holds the foreign key + if (($type === 'hasMany' || $type === 'hasOne') ^ $associatedThroughReference) { + $primaryAlias = $referenceAlias; + $foreignAlias = $modelAlias; + $primaryModel = $Reference; + $foreignModel = $Model; + } else { + $primaryAlias = $modelAlias; + $foreignAlias = $referenceAlias; + $primaryModel = $Model; + $foreignModel = $Reference; + } + + if ($associatedThroughReference) + $associationAlias = $referenceAlias; + else + $associationAlias = $modelAlias; + + $this->pr(30, + array('checkpoint' => 'Options/Association pre-merge', + compact('association', 'options'), + )); + + // A couple exceptions before performing a union of + // options and association. Namely, most fields result + // in either/or, but a couple should include BOTH the + // options AND the association settings. + foreach (array('fields', 'conditions') AS $fld) { + $this->pr(31, + array('checkpoint' => 'Options/Associations field original', + compact('fld') + + array("options[$fld]" => + array('value' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? $options[$fld] : '-null-') : '-unset-', + 'type' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? gettype($options[$fld]) : '-null-') : '-unset-'), + "association[$fld]" => + array('value' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? $association[$fld] : '-null-') : '-unset-', + 'type' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? gettype($association[$fld]) : '-null-') : '-unset-'), + ))); + + if (!isset($options[$fld]) || + (empty($options[$fld]) && !is_array($options[$fld]))) + unset($options[$fld]); + elseif (!empty($options[$fld]) && !is_array($options[$fld])) + $options[$fld] = array($options[$fld]); + + if (!isset($association[$fld]) || + (empty($association[$fld]) && !is_array($association[$fld]))) + unset($association[$fld]); + elseif (!empty($association[$fld]) && !is_array($association[$fld])) + $association[$fld] = array($association[$fld]); + + + $this->pr(31, + array('checkpoint' => 'Options/Associations field normalize', + compact('fld') + + array("options[$fld]" => + array('value' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? $options[$fld] : '-null-') : '-unset-', + 'type' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? gettype($options[$fld]) : '-null-') : '-unset-'), + "association[$fld]" => + array('value' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? $association[$fld] : '-null-') : '-unset-', + 'type' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? gettype($association[$fld]) : '-null-') : '-unset-'), + ))); + + if (isset($options[$fld]) && isset($association[$fld])) + $options[$fld] = array_merge($options[$fld], + $association[$fld]); + + $this->pr(31, + array('checkpoint' => 'Options/Associations field merge complete', + compact('fld') + + array("options[$fld]" => + array('value' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? $options[$fld] : '-null-') : '-unset-', + 'type' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? gettype($options[$fld]) : '-null-') : '-unset-'), + "association[$fld]" => + array('value' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? $association[$fld] : '-null-') : '-unset-', + 'type' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? gettype($association[$fld]) : '-null-') : '-unset-'), + ))); + } + + $this->pr(30, + array('checkpoint' => 'Options/Association post-merge', + compact('association', 'options'), + )); + + // For any option that's not already set, use + // whatever is specified by the assocation. + $options += array_intersect_key($association, $this->_options); + + // Replace all instances of the MODEL_ALIAS variable + // tag with the correct model alias. + $this->recursive_array_replace("%{MODEL_ALIAS}", + $associationAlias, + $options['conditions']); + + $this->pr(15, + array('checkpoint' => 'Models Established - Check Associations', + 'primaryModel' => $primaryAlias .' : '. $primaryModel->name, + 'foreignModel' => $foreignAlias .' : '. $foreignModel->name, + compact('type', 'association', 'options'), + )); + + if ($type === 'hasAndBelongsToMany') { + if (isset($association['with'])) + $linkClass = $association['with']; + else + $linkClass = Inflector::classify($association['joinTable']); + + $Link =& $Model->{$linkClass}; + + if (isset($options['linkalias'])) + $linkAlias = $options['linkalias']; + else + $linkAlias = $Link->alias; + + // foreignKey and associationForeignKey can refer to either + // the model or the reference, depending on which class + // actually defines the association. Make sure to we're + // using the foreign keys to point to the right class. + if ($associatedThroughReference) { + $modelAFK = 'associationForeignKey'; + $referenceAFK = 'foreignKey'; + } else { + $modelAFK = 'foreignKey'; + $referenceAFK = 'associationForeignKey'; + } + + $this->pr(17, + array('checkpoint' => 'Linking HABTM', + compact('linkClass', 'linkAlias', + 'modelAFK', 'referenceAFK'), + )); + + // Get the foreign key fields (for the link table) directly from + // the defined model associations, if they exists. This is the + // users direct specification, and therefore definitive if present. + $modelLink = $Link->escapeField($association[$modelAFK], $linkAlias); + $referenceLink = $Link->escapeField($association[$referenceAFK], $linkAlias); + + // If we haven't figured out the foreign keys, see if there is a + // model for the link table, and if it has the appropriate + // associations with the two tables we're trying to join. + if (empty($modelLink) && isset($Link->belongsTo[$Model->alias])) + $modelLink = $Link->escapeField($Link->belongsTo[$Model->alias]['foreignKey'], $linkAlias); + if (empty($referenceLink) && isset($Link->belongsTo[$Reference->alias])) + $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey'], $linkAlias); + + // We're running quite thin here. None of the models spell + // out the appropriate linkages. We'll have to SWAG it. + if (empty($modelLink)) + $modelLink = $Link->escapeField(Inflector::underscore($Model->alias) . '_id', $linkAlias); + if (empty($referenceLink)) + $referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id', $linkAlias); + + // Get the primary key from the tables we're joining. + $referenceKey = $Reference->escapeField(null, $referenceAlias); + $modelKey = $Model->escapeField(null, $modelAlias); + + $this->pr(21, + array('checkpoint' => 'HABTM links/keys', + array(compact('modelLink', 'modelKey'), + compact('referenceLink', 'referenceKey')), + )); + + // Join the linkage table to our model. We'll use an inner join, + // as the whole purpose of the linkage table is to make this + // connection. As we are embedding this join, the INNER will not + // cause any problem with the overall query, should the user not + // be concerned with whether or not the join has any results. + // They control that with the 'type' parameter which will be at + // the top level join. + $options['joins'][] = array('type' => 'INNER', + 'alias' => $modelAlias, + 'conditions' => "{$modelKey} = {$modelLink}", + 'table' => $db->fullTableName($Model, true)); + + // Now for the top level join. This will be added into the list + // of joins down below, outside of the HABTM specific code. + $options['class'] = $linkClass; + $options['alias'] = $linkAlias; + $options['table'] = $Link->getDataSource()->fullTableName($Link); + $options['conditions'][] = "{$referenceLink} = {$referenceKey}"; + } + elseif (isset($association['foreignKey']) && $association['foreignKey']) { + $foreignKey = $primaryModel->escapeField($association['foreignKey'], $primaryAlias); + $primaryKey = $foreignModel->escapeField($foreignModel->primaryKey, $foreignAlias); + + $this->pr(17, + array('checkpoint' => 'Linking due to foreignKey', + compact('foreignKey', 'primaryKey'), + )); + + // Only differentiating to help show the logical flow. + // Either way works and this test can be tossed out + if (($type === 'hasMany' || $type === 'hasOne') ^ $associatedThroughReference) + $options['conditions'][] = "{$primaryKey} = {$foreignKey}"; + else + $options['conditions'][] = "{$foreignKey} = {$primaryKey}"; + } + else { + $this->pr(17, + array('checkpoint' => 'Linking with no logic (expecting user defined)', + )); + + // No Foreign Key... nothing we can do. + } + + $this->pr(19, + array('checkpoint' => 'Conditions', + array('options[conditions]' => $options['conditions'], + ), + )); + + if (empty($options['table'])) { + $options['table'] = $db->fullTableName($Model, true); + } + + if (!isset($options['fields']) || !is_array($options['fields'])) + $options['fields'] = $db->fields($Model, $modelAlias); + elseif (!empty($options['fields'])) + $options['fields'] = $db->fields($Model, $modelAlias, $options['fields']); + + // When performing a count query, fields are useless. + // For everything else, we need to add them into the set. + if ($query_type !== 'count') + $result['fields'] = array_merge($result['fields'], $options['fields']); + + $result['joins'][] = array_intersect_key($options, + array('type' => true, + 'alias' => true, + 'table' => true, + 'joins' => true, + 'conditions' => true)); + + $sublinks = array_diff_key($options, $this->_options); + $this->buildQuery($Model, $modelClass, $modelAlias, $query_type, $sublinks, $result); + + $this->pr(19, + array('checkpoint' => 'Model Join Complete', + compact('referenceAlias', 'modelAlias', 'options', 'result'), + )); + } + + $this->pr(20, + array('return' => 'Linkable::buildQuery', + compact('referenceAlias'), + )); + } +} \ No newline at end of file diff --git a/models/contact.php b/models/contact.php new file mode 100644 index 0000000..b691823 --- /dev/null +++ b/models/contact.php @@ -0,0 +1,129 @@ + array( + 'joinTable' => 'contacts_methods', + 'associationForeignKey' => 'method_id', + 'unique' => true, + 'conditions' => "method = 'ADDRESS'", + ), + 'ContactPhone' => array( + 'joinTable' => 'contacts_methods', + 'associationForeignKey' => 'method_id', + 'unique' => true, + 'conditions' => "method = 'PHONE'", + ), + 'ContactEmail' => array( + 'joinTable' => 'contacts_methods', + 'associationForeignKey' => 'method_id', + 'unique' => true, + 'conditions' => "method = 'EMAIL'", + ), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: saveContact + * - Saves the contact and related data + */ + + function saveContact($id, $data) { + + // Establish a display name if not already given + if (!$data['Contact']['display_name']) + $data['Contact']['display_name'] = + (($data['Contact']['first_name'] && + $data['Contact']['last_name']) + ? $data['Contact']['last_name'] . ', ' . $data['Contact']['first_name'] + : ($data['Contact']['first_name'] + ? $data['Contact']['first_name'] + : $data['Contact']['last_name'])); + + // Save the contact data + $this->create(); + if ($id) + $this->id = $id; + if (!$this->save($data, false)) { + return false; + } + $id = $this->id; + + // Remove all associated ContactMethods, as it ensures + // any entries deleted by the user actually get deleted + // in the system. We'll recreate the needed ones anyway. + // REVISIT : 20090706 + // Appears that $this->save() is already doing the + // delete. I would have thought this would only happen + // on a saveAll?? +/* $this->ContactsMethod->deleteAll */ +/* (array('contact_id' => $id), false); */ + + // At this point, since we've saved data to contact, + // we'll proceed forward as much as possible, even + // if we encounter an error. For now, we'll assume + // the operation will succeed. + $ret = true; + + // Iterate each type of contact method, adding them into + // the database as needed and associating with this contact. + foreach (array('phone', 'address', 'email') AS $type) { + $class = 'Contact' . ucfirst($type); + $enum = strtoupper($type); + + // Nothing to do if this contact method isn't used + if (!isset($data[$class])) + continue; + + // Go through each entry of this contact method + foreach ($data[$class] AS &$item) { + + // If the user has entered all new data, we need to + // save that as a brand new entry. + if (!isset($item['id'])) { + $I = new $class(); + $I->create(); + if (!$I->save($item, false)) { + $ret = false; + continue; + } + $item['id'] = $I->id; + } + + // Update the ContactsMethod to reflect the appropriate IDs + $item['ContactsMethod']['contact_id'] = $id; + $item['ContactsMethod']['method_id'] = $item['id']; + $item['ContactsMethod']['method'] = $enum; + + // Save the relationship between contact and phone/email/address + $CM = new ContactsMethod(); + if (!$CM->save($item['ContactsMethod'], false)) { + $ret = false; + } + } + } + + // Return the result + return $ret; + } + + function contactList() { + return $this->find('list', + array('order' => + //array('last_name', 'first_name', 'middle_name'), + array('display_name'), + )); + } + +} +?> \ No newline at end of file diff --git a/models/contact_address.php b/models/contact_address.php new file mode 100644 index 0000000..22d4002 --- /dev/null +++ b/models/contact_address.php @@ -0,0 +1,46 @@ + array('numeric'), + 'postcode' => array('postal') + ); + + var $hasMany = array( + 'ContactsMethod' => array( + 'foreignKey' => 'method_id', + 'conditions' => "method = 'ADDRESS'", + ) + ); + + var $hasAndBelongsToMany = array( + 'Contact' => array( + 'className' => 'Contact', + 'joinTable' => 'contacts_methods', + 'foreignKey' => 'method_id', + 'associationForeignKey' => 'contact_id', + 'unique' => true, + 'conditions' => "method = 'ADDRESS'", + ) + ); + + function addressList() { + $results = $this->find('all', + array('contain' => false, + 'fields' => array('id', 'address', 'city', 'state', 'postcode'), + 'order' => array('state', 'city', 'postcode', 'address'))); + + $list = array(); + foreach ($results as $key => $val) { + $list[$val['ContactAddress']['id']] + = preg_replace("/\n/", ", ", $val['ContactAddress']['address']) + . ', ' . $val['ContactAddress']['city'] + . ', ' . $val['ContactAddress']['state'] + . ' ' . $val['ContactAddress']['postcode'] + ; + } + return $list; + } +} +?> \ No newline at end of file diff --git a/models/contact_email.php b/models/contact_email.php new file mode 100644 index 0000000..6a8e4a1 --- /dev/null +++ b/models/contact_email.php @@ -0,0 +1,27 @@ + array('numeric'), + 'email' => array('email') + ); + + var $hasAndBelongsToMany = array( + 'Contact' => array( + 'className' => 'Contact', + 'joinTable' => 'contacts_methods', + 'foreignKey' => 'method_id', + 'associationForeignKey' => 'contact_id', + 'unique' => true, + 'conditions' => "method = 'EMAIL'", + ) + ); + + function emailList() { + return $this->find('list'); + } + +} +?> \ No newline at end of file diff --git a/models/contact_phone.php b/models/contact_phone.php new file mode 100644 index 0000000..38e6169 --- /dev/null +++ b/models/contact_phone.php @@ -0,0 +1,46 @@ + array('numeric'), + //'type' => array('inlist'), + 'phone' => array('phone'), + 'ext' => array('numeric') + ); + + var $hasMany = array( + 'ContactsMethod' => array( + 'foreignKey' => 'method_id', + 'conditions' => "method = 'PHONE'", + ) + ); + + var $hasAndBelongsToMany = array( + 'Contact' => array( + 'className' => 'Contact', + 'joinTable' => 'contacts_methods', + 'foreignKey' => 'method_id', + 'associationForeignKey' => 'contact_id', + 'unique' => true, + 'conditions' => "method = 'PHONE'", + ) + ); + + function phoneList() { + $results = $this->find('all', + array('contain' => false, + 'fields' => array('id', 'phone', 'ext'), + 'order' => array('phone', 'ext'))); + + App::Import('Helper', 'Format'); + $list = array(); + foreach ($results as $key => $val) { + $list[$val['ContactPhone']['id']] + = FormatHelper::phone($val['ContactPhone']['phone'], + $val['ContactPhone']['ext']); + } + return $list; + } + +} +?> \ No newline at end of file diff --git a/models/contacts_customer.php b/models/contacts_customer.php new file mode 100644 index 0000000..515744a --- /dev/null +++ b/models/contacts_customer.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/models/contacts_method.php b/models/contacts_method.php new file mode 100644 index 0000000..ea07338 --- /dev/null +++ b/models/contacts_method.php @@ -0,0 +1,25 @@ + array( + 'foreignKey' => 'method_id', + 'conditions' => "method = 'ADDRESS'", + //'unique' => true, + ), + 'ContactPhone' => array( + 'foreignKey' => 'method_id', + 'conditions' => "method = 'PHONE'", + //'unique' => true, + ), + 'ContactEmail' => array( + 'foreignKey' => 'method_id', + 'conditions' => "method = 'EMAIL'", + //'unique' => true, + ), + ); + +} +?> \ No newline at end of file diff --git a/models/customer.php b/models/customer.php new file mode 100644 index 0000000..3df1ebb --- /dev/null +++ b/models/customer.php @@ -0,0 +1,377 @@ + array('numeric'), + 'name' => array('notempty'), + ); + + var $belongsTo = array( + 'PrimaryContact' => array( + 'className' => 'Contact', + ), + ); + + var $hasMany = array( + 'CurrentLease' => array( + 'className' => 'Lease', + 'conditions' => 'CurrentLease.close_date IS NULL', + ), + 'Lease', + 'StatementEntry', + 'ContactsCustomer', + + 'Transaction', + ); + + var $hasAndBelongsToMany = array( + 'Contact', + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: accountId + * - Returns the account ID for the given customer + */ + function accountId($id) { + $this->cacheQueries = true; + $customer = $this->find('first', + array('contain' => false, + 'fields' => array('account_id'), + 'conditions' => array(array('Customer.id' => $id)))); + $this->cacheQueries = false; + + return $customer['Customer']['account_id']; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: leaseIds + * - Returns the lease IDs for the given customer + */ + function leaseIds($id) { + $this->cacheQueries = true; + $customer = $this->find('first', + array('contain' => + array('Lease' => array('fields' => array('id'))), + 'fields' => array(), + 'conditions' => array(array('Customer.id' => $id)))); + $this->cacheQueries = false; + + $ids = array(); + foreach ($customer['Lease'] AS $lease) + $ids[] = $lease['id']; + + return $ids; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: securityDeposits + * - Returns an array of security deposit entries + */ + function securityDeposits($id, $query = null) { + $this->prEnter(compact('id', 'query')); + $this->queryInit($query); + + $query['conditions'][] = array('StatementEntry.customer_id' => $id); + $query['conditions'][] = array('StatementEntry.account_id' => + $this->StatementEntry->Account->securityDepositAccountID()); + + $set = $this->StatementEntry->reconciledSet('CHARGE', $query, false, true); + return $this->prReturn($set); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: securityDepositBalance + * - Returns the balance of the customer security deposit(s) + */ + + function securityDepositBalance($id, $query = null) { + $this->prEnter(compact('id', 'query')); + $this->queryInit($query); + + $sd_account_id = + $this->StatementEntry->Account->securityDepositAccountID(); + + $squery = $query; + $squery['conditions'][] = array('StatementEntry.customer_id' => $id); + $squery['conditions'][] = array('StatementEntry.account_id' => $sd_account_id); + $stats = $this->StatementEntry->stats(null, $squery); + $this->pr(26, compact('squery', 'stats')); + + // OK, we know now how much we charged for a security + // deposit, as well as how much we received to pay for it. + // Now we need to know if any has been released. + // Yes... this sucks. + $lquery = $query; + $lquery['link'] = array('Transaction' => + array('fields' => array(), + 'Customer' => + (empty($query['link']) + ? array('fields' => array()) + : $query['link']))); + $lquery['conditions'][] = array('Transaction.customer_id' => $id); + $lquery['conditions'][] = array('LedgerEntry.account_id' => $sd_account_id); + $lquery['conditions'][] = array('LedgerEntry.crdr' => 'DEBIT'); + $lquery['fields'][] = 'SUM(LedgerEntry.amount) AS total'; + $released = $this->StatementEntry->Transaction->LedgerEntry->find + ('first', $lquery); + $this->pr(26, compact('lquery', 'released')); + + return $this->prReturn($stats['Charge']['disbursement'] - $released[0]['total']); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: unreconciledCharges + * - Returns charges have not yet been fully paid + */ + + function unreconciledCharges($id, $query = null) { + $this->prEnter(compact('id', 'query')); + $this->queryInit($query); + + $query['conditions'][] = array('StatementEntry.customer_id' => $id); + $set = $this->StatementEntry->reconciledSet('CHARGE', $query, true); + return $this->prReturn($set); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: saveCustomer + * - Saves the customer and related data + */ + + function saveCustomer($id, $data, $primary_contact_entry) { + + // Go through each contact, and create new ones as needed + foreach ($data['Contact'] AS &$contact) { + if (isset($contact['id'])) + continue; + + $I = new Contact(); + if (!$I->saveContact(null, array('Contact' => $contact))) + return false; + $contact['id'] = $I->id; + } + + // Set the primary contact ID based on caller selection + $data['Customer']['primary_contact_id'] + = $data['Contact'][$primary_contact_entry]['id']; + + // Provide a default customer name if not specified + if (!$data['Customer']['name']) { + $this->Contact->recursive = -1; + $pcontact = $this->Contact->read(null, $data['Customer']['primary_contact_id']); + $data['Customer']['name'] = $pcontact['Contact']['display_name']; + } + + // Save the customer data + $this->create(); + if ($id) + $this->id = $id; + if (!$this->save($data, false)) { + return false; + } + $id = $this->id; + + // Remove all associated Customer Contacts, as it ensures + // any entries deleted by the user actually get deleted + // in the system. We'll recreate the needed ones anyway. + // REVISIT : 20090706 + // Appears that $this->save() is already doing the + // delete. I would have thought this would only happen + // on a saveAll?? +/* $this->ContactsCustomer->deleteAll */ +/* (array('customer_id' => $id), false); */ + + // At this point, since we've saved data to customer, + // we'll proceed forward as much as possible, even + // if we encounter an error. For now, we'll assume + // the operation will succeed. + $ret = true; + + // Go through each entry of this customer method + foreach ($data['Contact'] AS &$contact) { + // Update the ContactsCustomer to reflect the appropriate IDs + $contact['ContactsCustomer']['customer_id'] = $id; + $contact['ContactsCustomer']['contact_id'] = $contact['id']; + + // Save the relationship between customer and contact + $CM = new ContactsCustomer(); + if (!$CM->save($contact['ContactsCustomer'], false)) { + $ret = false; + } + } + + // Return the result + return $ret; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: update + * - Update any cached or calculated fields + */ + function update($id) { + $this->prEnter(compact('id')); + + if (empty($id)) { + $customers = $this->find('all', array('contain' => false, 'fields' => array('id'))); + foreach ($customers AS $customer) { + // This SHOULDN'T happen, but check to be sure + // or we'll get infinite recursion. + if (empty($customer['Customer']['id'])) + continue; + $this->update($customer['Customer']['id']); + } + return; + } + + // REVISIT : 20090812 + // updateLeaseCount is handled directly when needed. + // Should we simplify by just doing it anyway? + //$this->updateLeaseCount($id); + + $current_leases = + $this->find('all', + // REVISIT : 20090816 + // Do we need to update leases other than the current ones? + // It may be necessary. For example, a non-current lease + // can still be hit with an NSF item. In that case, it + // could have stale data if we look only to current leases. + //array('link' => array('CurrentLease' => array('type' => 'INNER')), + array('link' => array('Lease' => array('type' => 'INNER')), + 'conditions' => array('Customer.id' => $id))); + + foreach ($current_leases AS $lease) { + if (!empty($lease['CurrentLease']['id'])) + $this->Lease->update($lease['CurrentLease']['id']); + if (!empty($lease['Lease']['id'])) + $this->Lease->update($lease['Lease']['id']); + } + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: updateLeaseCount + * - Updates the internal lease count + */ + + function updateLeaseCount($id) { + $this->id = $id; + + $lease_count = + $this->find('count', + array('link' => array('Lease' => array('type' => 'INNER')), + 'conditions' => array('Customer.id' => $id))); + $current_count = + $this->find('count', + array('link' => array('CurrentLease' => array('type' => 'INNER')), + 'conditions' => array('Customer.id' => $id))); + + $this->saveField('lease_count', $lease_count); + $this->saveField('current_lease_count', $current_count); + $this->saveField('past_lease_count', $lease_count - $current_count); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: balance + * - Returns the balance of money owed on the lease + */ + + function balance($id) { + $stats = $this->stats($id); + return $stats['balance']; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested customer. + */ + + function stats($id = null, $query = null) { + //$this->prFunctionLevel(20); + $this->prEnter(compact('id', 'query')); + if (!$id) + return $this->prExit(null); + + $this->queryInit($query); + + // REVISIT : 20090725 + // We'll need to go directly to the statement entries if + // transactions are not always associated with the customer. + // This could happen if either we remove the customer_id + // field from Transaction, or we allow multiple customers + // to be part of the same transaction (essentially making + // the Transaction.customer_id meaningless). + +/* $stats = $this->StatementEntry->find */ +/* ('first', array */ +/* ('contain' => false, */ +/* 'fields' => $this->StatementEntry->chargeDisbursementFields(true), */ +/* 'conditions' => array('StatementEntry.customer_id' => $id), */ +/* )); */ + + $find_stats = $this->StatementEntry->find + ('first', array + ('contain' => false, + 'fields' => $this->StatementEntry->chargeDisbursementFields(true), + 'conditions' => array('StatementEntry.customer_id' => $id), + )); + $find_stats = $find_stats[0]; + $this->pr(17, compact('find_stats')); + + $tquery = $query; + $tquery['conditions'][] = array('StatementEntry.customer_id' => $id); + $statement_stats = $this->StatementEntry->stats(null, $tquery); + $statement_stats['balance'] = $statement_stats['Charge']['balance']; + $this->pr(17, compact('statement_stats')); + + $tquery = $query; + //$tquery['conditions'][] = array('StatementEntry.customer_id' => $id); + $tquery['conditions'][] = array('Transaction.customer_id' => $id); + $transaction_stats = $this->Transaction->stats(null, $tquery); + $transaction_stats += $transaction_stats['StatementEntry']; + $this->pr(17, compact('transaction_stats')); + + $tquery = $query; + //$tquery['conditions'][] = array('StatementEntry.customer_id' => $id); + $tquery['conditions'][] = array('Transaction.customer_id' => $id); + $ar_transaction_stats = $this->Transaction->stats(null, $tquery, + $this->Transaction->Account->accountReceivableAccountID()); + $ar_transaction_stats += $ar_transaction_stats['LedgerEntry']; + $this->pr(17, compact('ar_transaction_stats')); + + //$stats = $ar_transaction_stats; + $stats = $find_stats; + return $this->prReturn($stats); + } + +} +?> \ No newline at end of file diff --git a/models/double_entry.php b/models/double_entry.php new file mode 100644 index 0000000..999b283 --- /dev/null +++ b/models/double_entry.php @@ -0,0 +1,109 @@ + array( + 'className' => 'LedgerEntry', + ), + 'CreditEntry' => array( + 'className' => 'LedgerEntry', + ), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyDoubleEntry + * - Verifies consistenty of new double entry data + * (not in a pre-existing double entry) + */ + function verifyDoubleEntry($entry1, $entry2, $entry1_tender = null) { +/* pr(array("DoubleEntry::verifyDoubleEntry()" */ +/* => compact('entry1', 'entry2', 'entry1_tender'))); */ + + $LE = new LedgerEntry(); + if (!$LE->verifyLedgerEntry($entry1, $entry1_tender)) { +/* pr(array("DoubleEntry::verifyDoubleEntry()" */ +/* => "Entry1 verification failed")); */ + return false; + } + if (!$LE->verifyLedgerEntry($entry2)) { +/* pr(array("DoubleEntry::verifyDoubleEntry()" */ +/* => "Entry2 verification failed")); */ + return false; + } + + if (!(($entry1['crdr'] === 'DEBIT' && $entry2['crdr'] === 'CREDIT') || + ($entry1['crdr'] === 'CREDIT' && $entry2['crdr'] === 'DEBIT')) || + ($entry1['amount'] != $entry2['amount'])) { +/* pr(array("DoubleEntry::verifyDoubleEntry()" */ +/* => "Double Entry verification failed")); */ + return false; + } + + return true; + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addDoubleEntry + * - Inserts new Double Entry into the database + */ + + function addDoubleEntry($entry1, $entry2, $entry1_tender = null) { + //$this->prFunctionLevel(16); + $this->prEnter(compact('entry1', 'entry2', 'entry1_tender')); + + $ret = array(); + if (!$this->verifyDoubleEntry($entry1, $entry2, $entry1_tender)) + return $this->prReturn(array('error' => true) + $ret); + + // Handle the case where a double entry involves the same + // exact ledger. This would not serve any useful purpose. + // It is not, however, an error. It is semantically correct + // just not really logically correct. To make this easier, + // just ensure ledger_id is set for each entry, even though + // it would be handled later by the LedgerEntry model. + //array($entry1, $entry2) AS &$entry) { + for ($i=1; $i <= 2; ++$i) { + if (empty(${'entry'.$i}['ledger_id'])) + ${'entry'.$i}['ledger_id'] = + $this->DebitEntry->Account->currentLedgerID(${'entry'.$i}['account_id']); + } + if ($entry1['ledger_id'] == $entry2['ledger_id']) + return $this->prReturn(array('error' => false)); + + // Since this model only relates to DebitEntry and CreditEntry... + $LE = new LedgerEntry(); + + // Add the first ledger entry to the database + $result = $LE->addLedgerEntry($entry1, $entry1_tender); + $ret['Entry1'] = $result; + if ($result['error']) + return $this->prReturn(array('error' => true) + $ret); + + // Add the second ledger entry to the database + $result = $LE->addLedgerEntry($entry2); + $ret['Entry2'] = $result; + if ($result['error']) + return $this->prReturn(array('error' => true) + $ret); + + // Now link them as a double entry + $double_entry = array(); + $double_entry['debit_entry_id'] = + ($entry1['crdr'] === 'DEBIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id']; + $double_entry['credit_entry_id'] = + ($entry1['crdr'] === 'CREDIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id']; + + $ret['data'] = $double_entry; + + $this->create(); + if (!$this->save($double_entry)) + return $this->prReturn(array('error' => true) + $ret); + + $ret['double_entry_id'] = $this->id; + return $this->prReturn($ret + array('error' => false)); + } +} diff --git a/models/lease.php b/models/lease.php new file mode 100644 index 0000000..fd43dd3 --- /dev/null +++ b/models/lease.php @@ -0,0 +1,849 @@ + 30, 'show' => 30); + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: securityDeposits + * - Returns an array of security deposit entries + */ + function securityDeposits($id, $query = null) { + $this->prEnter(compact('id', 'query')); + $this->queryInit($query); + + $query['conditions'][] = array('StatementEntry.lease_id' => $id); + $query['conditions'][] = array('StatementEntry.account_id' => + $this->StatementEntry->Account->securityDepositAccountID()); + + $set = $this->StatementEntry->reconciledSet('CHARGE', $query, false, true); + return $this->prReturn($set); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: securityDepositBalance + * - Returns the balance of the lease security deposit(s) + */ + + function securityDepositBalance($id, $query = null) { + $this->prEnter(compact('id', 'query')); + $this->queryInit($query); + + // REVISIT : 20090807 + // Let's try simplifying the security deposit issue. + // Presume that security deposits are NOT used at all, + // until the customer moves out of the unit. At that + // time, the ENTIRE deposit is converted to customer + // credit. Piece of cake. + // For more information, see file revision history, + // including the revision just before this, r503. + + $this->id = $id; + $moveout_date = $this->field('moveout_date'); + if (!empty($moveout_date)) + return $this->prReturn(0); + + $query['conditions'][] = array('StatementEntry.lease_id' => $id); + $query['conditions'][] = array('StatementEntry.account_id' => + $this->StatementEntry->Account->securityDepositAccountID()); + + $stats = $this->StatementEntry->stats(null, $query); + return $this->prReturn($stats['Charge']['disbursement']); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: releaseSecurityDeposits + * - Releases all security deposits associated with this lease. + * That simply makes a disbursement out of them, which can be used + * to pay outstanding customer charges, or simply to become + * a customer surplus (customer credit). + */ + function releaseSecurityDeposits($id, $stamp = null, $query = null) { + //$this->prFunctionLevel(30); + $this->prEnter(compact('id', 'stamp', 'query')); + + $secdeps = $this->securityDeposits($id, $query); + $secdeps = $secdeps['entries']; + $this->pr(20, compact('secdeps')); + + // If there are no paid security deposits, then + // we can consider all security deposits released. + if (count($secdeps) == 0) + return $this->prReturn(true); + + // Build a transaction + $release = array('Transaction' => array(), 'Entry' => array()); + $release['Transaction']['stamp'] = $stamp; + $release['Transaction']['comment'] = "Security Deposit Release"; + foreach ($secdeps AS $charge) { + if ($charge['StatementEntry']['type'] !== 'CHARGE') + die("INTERNAL ERROR: SECURITY DEPOSIT IS NOT CHARGE"); + + // Since security deposits are being released, this also means + // any unpaid (or only partially paid) security deposit should + // have the remaining balance reversed. + if ($charge['StatementEntry']['balance'] > 0) + $this->StatementEntry->reverse($charge['StatementEntry']['id'], true, $stamp); + + $release['Entry'][] = + array('amount' => $charge['StatementEntry']['reconciled'], + 'account_id' => $this->StatementEntry->Account->securityDepositAccountID(), + 'comment' => "Released Security Deposit", + ); + } + + $customer_id = $secdeps[0]['StatementEntry']['customer_id']; + $lease_id = $secdeps[0]['StatementEntry']['lease_id']; + + // Add receipt of the security deposit funds. Do NOT + // flag them as part of the lease, as all received funds + // are only associated with the customer, for future + // (or present) disbursement on any lease. + $result = $this->StatementEntry->Transaction->addReceipt + ($release, $customer_id, null); + + return $this->prReturn($result); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: rentLastCharges + * - Returns a list of rent charges from this lease that + * do not have sequential followup charges. Under normal + * circumstances, there would only be one entry, which is + * the most recent rent charge. However, it's possible + * that there are several, indicating a problem with lease. + */ + + function rentLastCharges($id) { + $this->prEnter(compact('id')); + $rent_account_id = $this->StatementEntry->Account->rentAccountID(); + $entries = $this->find + ('all', + array('link' => + array(// Models + 'StatementEntry', + + 'SEx' => + array('class' => 'StatementEntry', + 'fields' => array(), + 'conditions' => array + ('SEx.effective_date = DATE_ADD(StatementEntry.through_date, INTERVAL 1 day)', + 'SEx.lease_id = StatementEntry.lease_id', + 'SEx.reverse_transaction_id IS NULL', + ), + ), + ), + + //'fields' => array('id', 'amount', 'effective_date', 'through_date'), + 'fields' => array(), + 'conditions' => array(array('Lease.id' => $id), + array('StatementEntry.type' => 'CHARGE'), + array('StatementEntry.account_id' => $rent_account_id), + array('StatementEntry.reverse_transaction_id IS NULL'), + array('SEx.id' => null), + ), + ) + ); + + return $this->prReturn($entries); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: lateCharges + * - Returns a list of late charges from this lease + */ + + function lateCharges($id) { + $this->prEnter(compact('id')); + $late_account_id = $this->StatementEntry->Account->lateChargeAccountID(); + $entries = $this->StatementEntry->find + ('all', + array('link' => + array(// Models + 'Lease', + ), + + //'fields' => array('id', 'amount', 'effective_date', 'through_date'), + 'conditions' => array(array('Lease.id' => $id), + array('StatementEntry.type' => 'CHARGE'), + array('StatementEntry.account_id' => $late_account_id), + ), + ) + ); + + return $this->prReturn($entries); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: rentChargeGaps + * - Checks for gaps in rent charges + */ + + function rentChargeGaps($id) { + $this->prEnter(compact('id')); + $entries = $this->rentLastCharges($id); + if ($entries && count($entries) > 1) + return $this->prReturn(true); + return $this->prReturn(false); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: rentChargeThrough + * - Determines the date that rent has been charged through + * Returns one of: + * null: There are gaps in the charges + * false: There are not yet any charges + * date: The date rent has been charged through + */ + + function rentChargeThrough($id) { + $this->prEnter(compact('id')); + $entries = $this->rentLastCharges($id); + if (!$entries) + return $this->prReturn(false); + if (count($entries) != 1) + return $this->prReturn(null); + return $this->prReturn($entries[0]['StatementEntry']['through_date']); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: rentPaidThrough + * - Determines the date of the first unpaid rent + */ + + function rentPaidThrough($id) { + $this->prEnter(compact('id')); + $rent_account_id = $this->StatementEntry->Account->rentAccountID(); + + // First, see if we can find any unpaid entries. Of course, + // the first unpaid entry gives us a very direct indication + // of when the customer is paid up through, which is 1 day + // prior to the effective date of that first unpaid charge. + $rent = $this->StatementEntry->reconciledSet + ('CHARGE', + array('fields' => + array('StatementEntry.*', + 'DATE_SUB(StatementEntry.effective_date, INTERVAL 1 DAY) AS paid_through', + ), + + 'conditions' => + array(array('StatementEntry.lease_id' => $id), + array('StatementEntry.account_id' => $rent_account_id), + array('StatementEntry.reverse_transaction_id IS NULL'), + ), + + 'order' => array('StatementEntry.effective_date'), + ), + true); + $this->pr(20, $rent, "Unpaid rent"); + + if ($rent['entries']) + return $this->prReturn($rent['entries'][0]['StatementEntry']['paid_through']); + + + // If we don't have any unpaid charges (great!), then the + // customer is paid up through the last day of the last + // charge. So, search for paid charges, which already + // have the paid through date saved as part of the entry. + $rent = $this->StatementEntry->reconciledSet + ('CHARGE', + array('conditions' => + array(array('StatementEntry.lease_id' => $id), + array('StatementEntry.account_id' => $rent_account_id), + array('StatementEntry.reverse_transaction_id IS NULL'), + ), + + 'order' => array('StatementEntry.through_date DESC'), + ), + false); + $this->pr(20, $rent, "Paid rent"); + + if ($rent['entries']) + return $this->prReturn($rent['entries'][0]['StatementEntry']['through_date']); + + + // After all that, having found that there are no unpaid + // charges, and in fact, no paid charges either, we cannot + // possibly say when the customer is paid through. + return $this->prReturn(null); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: assessMonthlyRent + * - Charges rent for the month, if not already charged. + */ + + function assessMonthlyRent($id, $date = null) { + $this->prEnter(compact('id', 'date')); + $this->id = $id; + + if (empty($date)) + $date = time(); + + if (is_string($date)) + $date = strtotime($date); + + // REVISIT : 20090808 + // Anniversary Billing not supported + $anniversary = 0 && $this->field('anniversary_billing'); + if (empty($anniversary)) { + $date_parts = getdate($date); + $date = mktime(0, 0, 0, $date_parts['mon'], 1, $date_parts['year']); + } + + // Make sure we're not trying to assess rent on a closed lease + $close_date = $this->field('close_date'); + $this->pr(17, compact('close_date')); + if (!empty($close_date)) + return $this->prReturn(null); + + // Don't assess rent after customer has moved out + $moveout_date = $this->field('moveout_date'); + $this->pr(17, compact('moveout_date')); + if (!empty($moveout_date) && strtotime($moveout_date) < $date) + return $this->prReturn(null); + + // Determine when the customer has already been charged through + // and, of course, don't charge them if they've already been. + $charge_through_date = strtotime($this->rentChargeThrough($id)); + $this->pr(17, compact('date', 'charge_through_date') + + array('date_str' => date('Y-m-d', $date), + 'charge_through_date_str' => date('Y-m-d', $charge_through_date))); + if ($charge_through_date >= $date) + return $this->prReturn(null); + + // OK, it seems we're going to go ahead and charge the customer + // on this lease. Calculate the new charge through date, which + // is 1 day shy of 1 month from $date. For example, if we're + // charging for 8/1/09, charge through will be 8/31/09, and + // charging for 8/15/09, charge through will be 9/14/09. + $date_parts = getdate($date); + $charge_through_date = mktime(0, 0, 0, + $date_parts['mon']+1, + $date_parts['mday']-1, + $date_parts['year']); + + // Build the invoice transaction + $invoice = array('Transaction' => array(), 'Entry' => array()); + // REVISIT : 20090808 + // Keeping Transaction.stamp until the existing facility + // is up to date. Then we want the stamp to be now() + // (and so can just delete the next line). + $invoice['Transaction']['stamp'] = date('Y-m-d', $date); + $invoice['Entry'][] = + array('effective_date' => date('Y-m-d', $date), + 'through_date' => date('Y-m-d', $charge_through_date), + 'amount' => $this->field('rent'), + 'account_id' => $this->StatementEntry->Account->rentAccountId(), + ); + + // Record the invoice and return the result + $this->pr(21, compact('invoice')); + $result = $this->StatementEntry->Transaction->addInvoice + ($invoice, null, $id); + return $this->prReturn($result); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: assessMonthlyRentAll + * - Ensures rent has been charged on all open leases + */ + + function assessMonthlyRentAll($date = null) { + $this->prEnter(compact('date')); + $leases = $this->find + ('all', array('contain' => false, + 'conditions' => array('Lease.close_date' => null), + )); + + $ret = array('Lease' => array()); + foreach ($leases AS $lease) { + $result = $this->assessMonthlyRent($lease['Lease']['id'], $date); + $ret['Lease'][$lease['Lease']['id']] = $result; + if ($result['error']) + $ret['error'] = true; + } + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: assessMonthlyLate + * - Assess late charges for the month, if not already charged. + */ + + function assessMonthlyLate($id, $date = null) { + $this->prEnter(compact('id', 'date')); + $this->id = $id; + + if (empty($date)) + $date = time(); + + if (is_string($date)) + $date = strtotime($date); + + // REVISIT : 20090808 + // Anniversary Billing not supported + $anniversary = 0 && $this->field('anniversary_billing'); + if (empty($anniversary)) { + $date_parts = getdate($date); + $date = mktime(0, 0, 0, $date_parts['mon'], 11, $date_parts['year']); + } + + // Don't assess a late charge if the late charge date hasn't + // even come yet. This is questionable whether we really + // should restrict, since the user could know what they're + // doing, and/or the server clock could be off (although that + // would certainly have much larger ramifications). But, the + // fact is that this check likely handles the vast majority + // of the expected behavior, and presents an issue for very + // few users, if any at all. + if ($date > time()) + return $this->prReturn(null); + + // Make sure we're not trying to assess late charges on a closed lease + $close_date = $this->field('close_date'); + $this->pr(17, compact('close_date')); + if (!empty($close_date)) + return $this->prReturn(null); + + // Don't assess late charges after customer has moved out + $moveout_date = $this->field('moveout_date'); + $this->pr(17, compact('moveout_date')); + if (!empty($moveout_date) && strtotime($moveout_date) < $date) + return $this->prReturn(null); + + // Determine when the customer has been charged through for rent + // and don't mark them as late if they haven't even been charged rent + $charge_through_date = strtotime($this->rentChargeThrough($id)); + $this->pr(17, compact('date', 'charge_through_date') + + array('date_str' => date('Y-m-d', $date), + 'charge_through_date_str' => date('Y-m-d', $charge_through_date))); + if ($charge_through_date <= $date) + return $this->prReturn(null); + + // Determine if the customer is actually late. This is based on + // when they've paid through, plus 10 days before they're late. + // REVISIT : 20090813 + // Of course, 10 days is a terrible hardcode. This should be + // driven from the late schedule, saved as part of the lease + // (when finally implemented). + $paid_through_date = strtotime($this->rentPaidThrough($id)); + $this->pr(17, compact('date', 'paid_through_date') + + array('date_str' => date('Y-m-d', $date), + 'paid_through_date_str' => date('Y-m-d', $paid_through_date))); + $date_parts = getdate($paid_through_date); + $paid_through_date = mktime(0, 0, 0, $date_parts['mon'], $date_parts['mday']+10, $date_parts['year']); + if ($paid_through_date >= $date) + return $this->prReturn(null); + + // Determine if the customer has already been charged a late fee + // and, of course, don't charge them if they've already been. + $late_charges = $this->lateCharges($id); + foreach ($late_charges AS $late) { + if (strtotime($late['StatementEntry']['effective_date']) == $date) + return $this->prReturn(null); + } + + // Build the invoice transaction + $invoice = array('Transaction' => array(), 'Entry' => array()); + // REVISIT : 20090808 + // Keeping Transaction.stamp until the existing facility + // is up to date. Then we want the stamp to be now() + // (and so can just delete the next line). + $invoice['Transaction']['stamp'] = date('Y-m-d', $date); + $invoice['Entry'][] = + array('effective_date' => date('Y-m-d', $date), + 'amount' => 10, + 'account_id' => $this->StatementEntry->Account->lateChargeAccountId(), + ); + + // Record the invoice and return the result + $this->pr(21, compact('invoice')); + $result = $this->StatementEntry->Transaction->addInvoice + ($invoice, null, $id); + return $this->prReturn($result); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: assessMonthlyLateAll + * - Ensures rent has been charged on all open leases + */ + + function assessMonthlyLateAll($date = null) { + $this->prEnter(compact('date')); + $leases = $this->find + ('all', array('contain' => false, + 'conditions' => array('Lease.close_date' => null), + )); + + $ret = array('Lease' => array()); + foreach ($leases AS $lease) { + $result = $this->assessMonthlyLate($lease['Lease']['id'], $date); + $ret['Lease'][$lease['Lease']['id']] = $result; + if ($result['error']) + $ret['error'] = true; + } + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * functions: delinquency + * - SQL fragments to determine whether a lease is delinquent + */ + + function conditionDelinquent($table_name = 'Lease') { + if (empty($table_name)) $t = ''; else $t = $table_name . '.'; + return ("({$t}close_date IS NULL AND" . + " NOW() > DATE_ADD({$t}paid_through_date, INTERVAL 10 DAY))"); + } + + function delinquentDaysSQL($table_name = 'Lease') { + if (empty($table_name)) $t = ''; else $t = $table_name . '.'; + return ("IF(" . $this->conditionDelinquent($table_name) . "," . + " DATEDIFF(NOW(), {$t}paid_through_date)-1," . + " NULL)"); + } + + function delinquentField($table_name = 'Lease') { + return ($this->delinquentDaysSQL($table_name) . " AS 'delinquent'"); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: moveIn + * - Moves the specified customer into the specified lease + */ + + function moveIn($customer_id, $unit_id, + $deposit = null, $rent = null, + $stamp = null, $comment = null) + { + $this->prEnter(compact('customer_id', 'unit_id', + 'deposit', 'rent', 'stamp', 'comment')); + + $lt = $this->LeaseType->find('first', + array('conditions' => + array('code' => 'SL'))); + + // Use NOW if not given a movein date + if (!isset($stamp)) + $stamp = date('Y-m-d G:i:s'); + + if (!$comment) + $comment = null; + + if (!isset($deposit) || !isset($rent)) { + $rates = $this->Unit->find + ('first', + array('contain' => + array('UnitSize' => + array('deposit', 'rent'), + ), + 'fields' => array('deposit', 'rent'), + 'conditions' => array('Unit.id' => $unit_id), + )); + + $deposit = + (isset($deposit) + ? $deposit + : (isset($rates['Unit']['deposit']) + ? $rates['Unit']['deposit'] + : (isset($rates['UnitSize']['deposit']) + ? $rates['UnitSize']['deposit'] + : 0))); + + $rent = + (isset($rent) + ? $rent + : (isset($rates['Unit']['rent']) + ? $rates['Unit']['rent'] + : (isset($rates['UnitSize']['rent']) + ? $rates['UnitSize']['rent'] + : 0))); + } + + + // Save this new lease. + $this->create(); + if (!$this->save(array('lease_type_id' => $lt['LeaseType']['id'], + 'unit_id' => $unit_id, + 'customer_id' => $customer_id, + 'lease_date' => $stamp, + 'movein_date' => $stamp, + 'deposit' => $deposit, + 'rent' => $rent, + 'comment' => $comment), false)) { + return $this->prReturn(null); + } + + // Set the lease number to be the same as the lease ID + $this->id; + $this->saveField('number', $this->id); + + // Update the current lease count for the customer + $this->Customer->updateLeaseCount($customer_id); + + // Update the unit status + $this->Unit->updateStatus($unit_id, 'OCCUPIED'); + + // REVISIT : 20090702 + // We need to assess the security deposit charge, + // and probably rent as well. Rent, however, will + // require user parameters to indicate whether it + // was waived, pro-rated, etc. + + // Return the new lease ID + return $this->prReturn($this->id); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: moveOut + * - Moves the customer out of the specified lease + */ + + function moveOut($id, $status = 'VACANT', + $stamp = null, $close = true) + { + $this->prEnter(compact('id', 'status', 'stamp', 'close')); + + // Use NOW if not given a moveout date + if (!isset($stamp)) + $stamp = date('Y-m-d G:i:s'); + + // Reset the data + $this->create(); + $this->id = $id; + + // Set the customer move-out date + $this->data['Lease']['moveout_date'] = $stamp; + + // Save it! + $this->save($this->data, false); + + // Release the security deposit(s) + $this->releaseSecurityDeposits($id, $stamp); + + // Close the lease, if so requested + if ($close) + $this->close($id, $stamp); + + // Update the current lease count for the customer + $this->Customer->updateLeaseCount($this->field('customer_id')); + + // Finally, update the unit status + $this->recursive = -1; + $this->read(); + $this->Unit->updateStatus($this->data['Lease']['unit_id'], $status); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: close + * - Closes the lease to further action + */ + + function close($id, $stamp = null) { + $this->prEnter(compact('id', 'stamp')); + + if (!$this->closeable($id)) + return $this->prReturn(false); + + // Reset the data + $this->create(); + $this->id = $id; + + // Use NOW if not given a moveout date + if (!isset($stamp)) + $stamp = date('Y-m-d G:i:s'); + + // Set the close date + $this->data['Lease']['close_date'] = $stamp; + + // Save it! + $this->save($this->data, false); + + // Update the current lease count for the customer + $this->Customer->updateLeaseCount($this->field('customer_id')); + + return $this->prReturn(true); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: closeable + * - Indicates whether or not the lease can be closed + */ + + function closeable($id) { + $this->prEnter(compact('id')); + + $this->recursive = -1; + $this->read(null, $id); + + // We can't close a lease that's still in use + if (!isset($this->data['Lease']['moveout_date'])) + return $this->prReturn(false); + + // We can't close a lease that's already closed + if (isset($this->data['Lease']['close_date'])) + return $this->prReturn(false); + + // A lease can only be closed if there are no outstanding + // security deposits ... + if ($this->securityDepositBalance($id) != 0) + return $this->prReturn(false); + + // ... and if the account balance is zero. + if ($this->balance($id) != 0) + return $this->prReturn(false); + + // Apparently this lease meets all the criteria! + return $this->prReturn(true); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: refund + * - Marks any lease balance as payable to the customer. + */ + + function refund($id, $stamp = null) { + $this->prEnter(compact('id')); + $balance = $this->balance($id); + + if ($balance >= 0) + return $this->prReturn(array('error' => true)); + + $balance *= -1; + + // Build a transaction + $refund = array('Transaction' => array(), 'Entry' => array()); + $refund['Transaction']['stamp'] = $stamp; + $refund['Transaction']['comment'] = "Lease Refund"; + + $refund['Entry'][] = + array('amount' => $balance); + + $result = $this->StatementEntry->Transaction->addRefund + ($refund, null, $id); + + return $this->prReturn($result); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: update + * - Update any cached or calculated fields + */ + function update($id) { + $this->prEnter(compact('id')); + + $this->id = $id; + $this->saveField('charge_through_date', $this->rentChargeThrough($id)); + $this->saveField('paid_through_date', $this->rentPaidThrough($id)); + + $moveout = $this->field('moveout_date'); + if (empty($moveout)) + $this->Unit->update($this->field('unit_id')); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: balance + * - Returns the balance of money owed on the lease + */ + + function balance($id) { + $this->prEnter(compact('id')); + $stats = $this->stats($id); + return $this->prReturn($stats['balance']); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested lease. + */ + + function stats($id = null, $query = null) { + $this->prEnter(compact('id', 'query')); + if (!$id) + return $this->prReturn(null); + + $find_stats = $this->StatementEntry->find + ('first', array + ('contain' => false, + 'fields' => $this->StatementEntry->chargeDisbursementFields(true), + 'conditions' => array('StatementEntry.lease_id' => $id), + )); + $find_stats = $find_stats[0]; + return $this->prReturn($find_stats); + } + +} +?> \ No newline at end of file diff --git a/models/lease_type.php b/models/lease_type.php new file mode 100644 index 0000000..e606b80 --- /dev/null +++ b/models/lease_type.php @@ -0,0 +1,15 @@ + array('numeric'), + 'name' => array('notempty') + ); + + var $hasMany = array( + 'Lease', + ); + +} +?> \ No newline at end of file diff --git a/models/ledger.php b/models/ledger.php new file mode 100644 index 0000000..4213351 --- /dev/null +++ b/models/ledger.php @@ -0,0 +1,179 @@ + array('className' => 'Ledger'), + 'CloseTransaction' => array('className' => 'Transaction'), + ); + + var $hasMany = array( + 'Transaction', + 'LedgerEntry', + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: accountID + * - Returns the account ID for the given ledger + */ + function accountID($id) { + $this->cacheQueries = true; + $item = $this->find('first', array + ('link' => array('Account'), + 'conditions' => array('Ledger.id' => $id), + )); + $this->cacheQueries = false; + //pr(compact('id', 'item')); + return $item['Account']['id']; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: currentLedgerID + * - Returns the current ledger ID of the account for the given ledger. + */ + function currentLedgerID($id) { + return $this->Account->currentLedgerID($this->accountID($id)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: closeLedger + * - Closes the current ledger, and returns a fresh one + */ + function closeLedgers($ids) { + $ret = array('new_ledger_ids' => array()); + + $entries = array(); + foreach ($ids AS $id) { + // Query stats to get the balance forward + $stats = $this->stats($id); + + // Populate fields from the current ledger + $this->recursive = -1; + $this->id = $id; + $this->read(); + + // Build a new ledger to replace the current one + $this->data['Ledger']['id'] = null; + $this->data['Ledger']['close_transaction_id'] = null; + $this->data['Ledger']['prior_ledger_id'] = $id; + $this->data['Ledger']['comment'] = null; + ++$this->data['Ledger']['sequence']; + $this->data['Ledger']['name'] = + ($this->data['Ledger']['account_id'] . + '-' . + $this->data['Ledger']['sequence']); + + // Save the new ledger + $this->id = null; + if (!$this->save($this->data, false)) + return array('error' => true, 'new_ledger_data' => $this->data) + $ret; + $ret['new_ledger_ids'][] = $this->id; + + $entries[] = array('old_ledger_id' => $id, + 'new_ledger_id' => $this->id, + 'amount' => $stats['balance']); + } + + // Perform the close + $result = $this->Transaction->addClose(array('Transaction' => array(), + 'Ledger' => $entries)); + $ret['Transaction'] = $result; + if ($result['error']) + return array('error' => true) + $ret; + + return $ret + array('error' => false); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: debitCreditFields + * - Returns the fields necessary to determine whether the queried + * entries are a debit, or a credit, and also the effect each have + * on the overall balance of the ledger. + */ + function debitCreditFields($sum = false, $balance = true, + $entry_name = 'LedgerEntry', $account_name = 'Account') { + return $this->LedgerEntry->debitCreditFields + ($sum, $balance, $entry_name, $account_name); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: ledgerEntries + * - Returns an array of ledger entries that belong to a given + * ledger. There is extra work done to establish debit/credit + */ + function ledgerEntries($ids, $query = null) { + if (empty($ids)) + return null; + + $entries = $this->LedgerEntry->find + ('all', array + ('link' => array('Ledger' => array('Account')), + 'fields' => array_merge(array("LedgerEntry.*"), + $this->LedgerEntry->debitCreditFields()), + 'conditions' => array('LedgerEntry.ledger_id' => $ids), + )); + + //pr(compact('entries')); + return $entries; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested ledger. + */ + function stats($id, $query = null) { + if (!$id) + return null; + + $this->queryInit($query); + + if (!isset($query['link']['Account'])) + $query['link']['Account'] = array(); + if (!isset($query['link']['Account']['fields'])) + $query['link']['Account']['fields'] = array(); + if (!isset($query['fields'])) + $query['fields'] = array(); + + $query['fields'] = array_merge($query['fields'], + $this->debitCreditFields(true)); + + $query['conditions'][] = array('LedgerEntry.ledger_id' => $id); + $query['group'][] = 'LedgerEntry.ledger_id'; + + $stats = $this->LedgerEntry->find('first', $query); + + // The fields are all tucked into the [0] index, + // and the rest of the array is useless (empty). + $stats = $stats[0]; + + // Make sure we have a member for debit/credit + foreach(array('debits', 'credits') AS $crdr) + if (!isset($stats[$crdr])) + $stats[$crdr] = null; + + // Make sure we have a non-null balance + if (!isset($stats['balance'])) + $stats['balance'] = 0; + + return $stats; + } + +} +?> \ No newline at end of file diff --git a/models/ledger_entry.php b/models/ledger_entry.php new file mode 100644 index 0000000..096ca19 --- /dev/null +++ b/models/ledger_entry.php @@ -0,0 +1,177 @@ + array( + 'dependent' => true, + ), + 'DebitDoubleEntry' => array( + 'className' => 'DoubleEntry', + 'foreignKey' => 'debit_entry_id', + 'dependent' => true, + ), + 'CreditDoubleEntry' => array( + 'className' => 'DoubleEntry', + 'foreignKey' => 'credit_entry_id', + 'dependent' => true, + ), + 'DoubleEntry' => array( + 'foreignKey' => false, + ), + ); + + var $hasMany = array( + ); + + var $hasAndBelongsToMany = array( + // The Debit half of the double entry matching THIS Credit (if it is one) + 'DebitEntry' => array( + 'className' => 'LedgerEntry', + 'joinTable' => 'double_entries', + 'linkalias' => 'DDE', + 'foreignKey' => 'credit_entry_id', + 'associationForeignKey' => 'debit_entry_id', + ), + + // The Credit half of the double entry matching THIS Debit (if it is one) + 'CreditEntry' => array( + 'className' => 'LedgerEntry', + 'joinTable' => 'double_entries', + 'linkalias' => 'CDE', + 'foreignKey' => 'debit_entry_id', + 'associationForeignKey' => 'credit_entry_id', + ), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: debitCreditFields + * - Returns the fields necessary to determine whether the queried + * entries are a debit, or a credit, and also the effect each have + * on the overall balance of the account/ledger. + */ + + function debitCreditFields($sum = false, $balance = true, + $entry_name = 'LedgerEntry', $account_name = 'Account') { + $fields = array + ( + ($sum ? 'SUM(' : '') . + "IF({$entry_name}.crdr = 'DEBIT'," . + " {$entry_name}.amount, NULL)" . + ($sum ? ')' : '') . ' AS debit' . ($sum ? 's' : ''), + + ($sum ? 'SUM(' : '') . + "IF({$entry_name}.crdr = 'CREDIT'," . + " {$entry_name}.amount, NULL)" . + ($sum ? ')' : '') . ' AS credit' . ($sum ? 's' : ''), + ); + + if ($balance) + $fields[] = + ($sum ? 'SUM(' : '') . + "IF(${account_name}.type IN ('ASSET', 'EXPENSE')," . + " IF({$entry_name}.crdr = 'DEBIT', 1, -1)," . + " IF({$entry_name}.crdr = 'CREDIT', 1, -1))" . + " * IF({$entry_name}.amount, {$entry_name}.amount, 0)" . + ($sum ? ')' : '') . ' AS balance'; + + if ($sum) + $fields[] = "COUNT({$entry_name}.id) AS entries"; + + return $fields; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyLedgerEntry + * - Verifies consistenty of new ledger entry data + * (not in a pre-existing ledger entry) + */ + function verifyLedgerEntry($entry, $tender = null) { +/* pr(array("LedgerEntry::verifyLedgerEntry()" */ +/* => compact('entry', 'tender'))); */ + + if (empty($entry['account_id']) || + empty($entry['crdr']) || + empty($entry['amount']) + ) { +/* pr(array("LedgerEntry::verifyLedgerEntry()" */ +/* => "Entry verification failed")); */ + return false; + } + + if (isset($tender) && !$this->Tender->verifyTender($tender)) { +/* pr(array("LedgerEntry::verifyLedgerEntry()" */ +/* => "Tender verification failed")); */ + return false; + } + + return true; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addLedgerEntry + * - Inserts new Ledger Entry into the database + */ + function addLedgerEntry($entry, $tender = null) { + //$this->prFunctionLevel(16); + $this->prEnter(compact('entry', 'tender')); + + $ret = array('data' => $entry); + if (!$this->verifyLedgerEntry($entry, $tender)) + return $this->prReturn(array('error' => true) + $ret); + + if (empty($entry['ledger_id'])) + $entry['ledger_id'] = + $this->Account->currentLedgerID($entry['account_id']); + + $this->create(); + if (!$this->save($entry)) + return $this->prReturn(array('error' => true) + $ret); + + $ret['ledger_entry_id'] = $this->id; + + if (isset($tender)) { + $tender['account_id'] = $entry['account_id']; + $tender['ledger_entry_id'] = $ret['ledger_entry_id']; + $result = $this->Tender->addTender($tender); + $ret['Tender'] = $result; + if ($result['error']) + return $this->prReturn(array('error' => true) + $ret); + } + + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested ledger entry + */ + function stats($id = null, $query = null, $set = null) { + $this->queryInit($query); + + // REVISIT : 20090816 + // This function appeared to be dramatically broken, + // a throwback to an earlier time. I deleted its + // contents and added this error to ensure it does + // not get used. + $this->INTERNAL_ERROR('This function should not be used'); + } + +} diff --git a/models/map.php b/models/map.php new file mode 100644 index 0000000..2e14359 --- /dev/null +++ b/models/map.php @@ -0,0 +1,23 @@ + array('numeric'), + 'site_id' => array('numeric'), + 'site_area_id' => array('numeric'), + 'name' => array('notempty'), + 'width' => array('numeric'), + 'depth' => array('numeric') + ); + + var $belongsTo = array( + 'SiteArea', + ); + + var $hasAndBelongsToMany = array( + 'Unit', + ); + +} +?> \ No newline at end of file diff --git a/models/site.php b/models/site.php new file mode 100644 index 0000000..4825faa --- /dev/null +++ b/models/site.php @@ -0,0 +1,16 @@ + array('numeric'), + 'name' => array('notempty') + ); + + var $hasMany = array( + 'SiteArea', + 'SiteOption', + ); + +} +?> \ No newline at end of file diff --git a/models/site_area.php b/models/site_area.php new file mode 100644 index 0000000..2303946 --- /dev/null +++ b/models/site_area.php @@ -0,0 +1,20 @@ + array('numeric'), + 'site_id' => array('numeric'), + 'name' => array('notempty') + ); + + var $belongsTo = array( + 'Site', + ); + + var $hasOne = array( + 'Map', + ); + +} +?> \ No newline at end of file diff --git a/models/statement_entry.php b/models/statement_entry.php new file mode 100644 index 0000000..9d58f56 --- /dev/null +++ b/models/statement_entry.php @@ -0,0 +1,725 @@ + array( + 'className' => 'StatementEntry', + ), + ); + + var $hasMany = array( + // The disbursements that apply to this charge (if it is one) + 'DisbursementEntry' => array( + 'className' => 'StatementEntry', + 'foreignKey' => 'charge_entry_id', + 'dependent' => true, + ), + + ); + + //var $default_log_level = array('log' => 30, 'show' => 15); + var $max_log_level = 19; + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: debit/creditTypes + */ + + function debitTypes() { + return array('CHARGE', 'PAYMENT', 'REFUND'); + } + + function creditTypes() { + return array('DISBURSEMENT', 'WAIVER', 'REVERSAL', 'WRITEOFF', 'SURPLUS'); + } + + function voidTypes() { + return array('VOID'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: chargeDisbursementFields + */ + + function chargeDisbursementFields($sum = false, $entry_name = 'StatementEntry') { + $debits = $this->debitTypes(); + $credits = $this->creditTypes(); + $voids = $this->voidTypes(); + + foreach ($debits AS &$enum) + $enum = "'" . $enum . "'"; + foreach ($credits AS &$enum) + $enum = "'" . $enum . "'"; + foreach ($voids AS &$enum) + $enum = "'" . $enum . "'"; + + $debit_set = implode(", ", $debits); + $credit_set = implode(", ", $credits); + $void_set = implode(", ", $voids); + + $fields = array + ( + ($sum ? 'SUM(' : '') . + "IF({$entry_name}.type IN ({$debit_set})," . + " {$entry_name}.amount, NULL)" . + ($sum ? ')' : '') . ' AS charge' . ($sum ? 's' : ''), + + ($sum ? 'SUM(' : '') . + "IF({$entry_name}.type IN({$credit_set})," . + " {$entry_name}.amount, NULL)" . + ($sum ? ')' : '') . ' AS disbursement' . ($sum ? 's' : ''), + + ($sum ? 'SUM(' : '') . + "IF({$entry_name}.type IN ({$debit_set}), 1," . + " IF({$entry_name}.type IN ({$credit_set}), -1, 0))" . + " * IF({$entry_name}.amount, {$entry_name}.amount, 0)" . + ($sum ? ')' : '') . ' AS balance', + ); + + if ($sum) + $fields[] = "COUNT({$entry_name}.id) AS entries"; + + return $fields; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyStatementEntry + * - Verifies consistenty of new statement entry data + * (not in a pre-existing statement entry) + */ + function verifyStatementEntry($entry) { + $this->prFunctionLevel(10); + $this->prEnter(compact('entry')); + + if (empty($entry['type']) || + //empty($entry['effective_date']) || + empty($entry['account_id']) || + empty($entry['amount']) + ) { + return $this->prReturn(false); + } + + return $this->prReturn(true); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addStatementEntry + * - Inserts new Statement Entry into the database + */ + function addStatementEntry($entry) { + $this->prEnter(compact('entry')); + + $ret = array('data' => $entry); + if (!$this->verifyStatementEntry($entry)) + return $this->prReturn(array('error' => true, 'verify_data' => $entry) + $ret); + + $this->create(); + if (!$this->save($entry)) + return $this->prReturn(array('error' => true, 'save_data' => $entry) + $ret); + + $ret['statement_entry_id'] = $this->id; + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: waive + * - Waives the charge balance + * + */ + function waive($id, $stamp = null) { + $this->prEnter(compact('id', 'stamp')); + + // Get the basic information about the entry to be waived. + $this->recursive = -1; + $charge = $this->read(null, $id); + $charge = $charge['StatementEntry']; + + if ($charge['type'] !== 'CHARGE') + $this->INTERNAL_ERROR("Waiver item is not CHARGE."); + + // Query the stats to get the remaining balance + $stats = $this->stats($id); + + // Build a transaction + $waiver = array('Transaction' => array(), 'Entry' => array()); + $waiver['Transaction']['stamp'] = $stamp; + $waiver['Transaction']['comment'] = "Charge Waiver"; + + // Add the charge waiver + $waiver['Entry'][] = + array('amount' => $stats['Charge']['balance'], + 'comment' => null, + ); + + // Record the waiver transaction + return $this->prReturn($this->Transaction->addWaiver + ($waiver, $id, $charge['customer_id'], $charge['lease_id'])); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: reversable + * - Returns true if the charge can be reversed; false otherwise + */ + function reversable($id) { + $this->prEnter(compact('id')); + + if (empty($id)) + return $this->prReturn(false); + + // Verify the item is an actual charge + $this->id = $id; + $charge_type = $this->field('type'); + if ($charge_type !== 'CHARGE') + return $this->prReturn(false); + + // Determine anything reconciled against the charge + $reverse_transaction_id = $this->field('reverse_transaction_id'); + if (!empty($reverse_transaction_id)) + return $this->prReturn(false); + + return $this->prReturn(true); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: reverse + * - Reverses the charges + */ + function reverse($id, $stamp = null, $comment) { + $this->prEnter(compact('id', 'stamp')); + + // Verify the item can be reversed + if (!$this->reversable($id)) + $this->INTERNAL_ERROR("Item is not reversable."); + + // Get the basic information about this charge + $charge = $this->find('first', array('contain' => true)); + //$charge = $charge['StatementEntry']; + + // Query the stats to get the remaining balance + $stats = $this->stats($id); + $charge['paid'] = $stats['Charge']['disbursement']; + + // Record the reversal transaction + $result = $this->Transaction->addReversal + ($charge, $stamp, $comment ? $comment : 'Charge Reversal'); + + if (empty($result['error'])) { + // Mark the charge as reversed + $this->id = $id; + $this->saveField('reverse_transaction_id', $result['transaction_id']); + } + + return $this->prReturn($result); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: reconciledSet + * - Returns the set of entries satisfying the given conditions, + * along with any entries that they reconcile + */ + function reconciledSetQuery($set, $query) { + $this->queryInit($query); + + if (in_array($set, $this->debitTypes())) + $query['link']['DisbursementEntry'] = array('fields' => array("SUM(DisbursementEntry.amount) AS reconciled")); + elseif (in_array($set, $this->creditTypes())) + $query['link']['ChargeEntry'] = array('fields' => array("SUM(ChargeEntry.amount) AS reconciled")); + else + die("INVALID RECONCILE SET"); + + $query['conditions'][] = array('StatementEntry.type' => $set); + $query['group'] = 'StatementEntry.id'; + + return $query; + } + + function reconciledSet($set, $query = null, $unrec = false, $if_rec_include_partial = false) { + //$this->prFunctionLevel(array('log' => 16, 'show' => 10)); + $this->prEnter(compact('set', 'query', 'unrec', 'if_rec_include_partial')); + $lquery = $this->reconciledSetQuery($set, $query); + $result = $this->find('all', $lquery); + + $this->pr(20, compact('lquery', 'result')); + + $resultset = array(); + foreach ($result AS $i => $entry) { + $this->pr(25, compact('entry')); + if (!empty($entry[0])) + $entry['StatementEntry'] = $entry[0] + $entry['StatementEntry']; + unset($entry[0]); + + $entry['StatementEntry']['balance'] = + $entry['StatementEntry']['amount'] - $entry['StatementEntry']['reconciled']; + + // Since HAVING isn't a builtin feature of CakePHP, + // we'll have to post-process to get the desired entries + + if ($entry['StatementEntry']['balance'] == 0) + $reconciled = true; + elseif ($entry['StatementEntry']['reconciled'] == 0) + $reconciled = false; + else // Partial disbursement; depends on unrec + $reconciled = (!$unrec && $if_rec_include_partial); + + // Add to the set, if it's been requested + if ($reconciled == !$unrec) + $resultset[] = $entry; + } + + return $this->prReturn(array('entries' => $resultset, + 'summary' => $this->stats(null, $query))); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: reconciledEntries + * - Returns a list of entries that reconcile against the given entry. + * (such as disbursements towards a charge). + */ + function reconciledEntriesQuery($id, $query = null) { + $this->queryInit($query, false); + + $this->id = $id; + $this->recursive = -1; + $this->read(); + + $query['conditions'][] = array('StatementEntry.id' => $id); + + if (in_array($this->data['StatementEntry']['type'], $this->debitTypes())) { + $query['link']['DisbursementEntry'] = array(); + $query['conditions'][] = array('DisbursementEntry.id !=' => null); + } + if (in_array($this->data['StatementEntry']['type'], $this->creditTypes())) { + $query['link']['ChargeEntry'] = array(); + $query['conditions'][] = array('ChargeEntry.id !=' => null); + } + + return $query; + } + + function reconciledEntries($id, $query = null) { + $this->prEnter(compact('id', 'query')); + $lquery = $this->reconciledEntriesQuery($id, $query); + + $result = $this->find('all', $lquery); + foreach (array_keys($result) AS $i) + unset($result[$i]['StatementEntry']); + + return $this->prReturn(array('entries' => $result)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: assignCredits + * - Assigns all credits to existing charges + * + * REVISIT : 20090726 + * This algorithm shouldn't be hardcoded. We need to allow + * the user to specify how disbursements should be applied. + * + */ + function assignCredits($query = null, $receipt_id = null, + $charge_ids = null, $disbursement_type = null, + $customer_id = null, $lease_id = null) + { + //$this->prFunctionLevel(25); + $this->prEnter(compact('query', 'receipt_id', + 'charge_ids', 'disbursement_type', + 'customer_id', 'lease_id')); + $this->queryInit($query); + + if (!empty($customer_id)) + $query['conditions'][] = array('StatementEntry.customer_id' => $customer_id); + + if (empty($disbursement_type)) + $disbursement_type = 'DISBURSEMENT'; + + $ret = array(); + + // First, find all known credits, unless this call is to make + // credit adjustments to a specific charge + if (empty($receipt_id)) { + + if (!empty($charge_ids)) + $this->INTERNAL_ERROR("Charge IDs, yet no corresponding receipt"); + + $lquery = $query; + $lquery['conditions'][] = array('StatementEntry.type' => 'SURPLUS'); + // REVISIT : 20090804 + // We need to ensure that we're using surplus credits ONLY from either + // the given lease, or those that do not apply to any specific lease. + // However, by doing this, it forces any lease surplus amounts to + // remain frozen with that lease until either there is a lease charge, + // we refund the money, or we "promote" that surplus to the customer + // level and out of the leases direct control. + // That seems like a pain. Perhaps we should allow any customer + // surplus to be used on any customer charge. + $lquery['conditions'][] = + array('OR' => + array(array('StatementEntry.lease_id' => null), + (!empty($lease_id) + ? array('StatementEntry.lease_id' => $lease_id) + : array()), + )); + $lquery['order'][] = 'StatementEntry.effective_date ASC'; + $credits = $this->find('all', $lquery); + $this->pr(18, compact('credits'), + "Credits Established"); + } + else { + // Establish credit from the (newly added) receipt + $lquery = + array('link' => + array('StatementEntry', + 'LedgerEntry' => + array('conditions' => + array('LedgerEntry.account_id <> Transaction.account_id') + ), + ), + 'conditions' => array('Transaction.id' => $receipt_id), + 'fields' => array('Transaction.id', 'Transaction.stamp', 'Transaction.amount'), + ); + $receipt_credit = $this->Transaction->find('first', $lquery); + if (!$receipt_credit) + $this->INTERNAL_ERROR("Unable to locate receipt."); + + $stats = $this->Transaction->stats($receipt_id); + $receipt_credit['balance'] = $stats['undisbursed']; + + $receipt_credit['receipt'] = true; + $credits = array($receipt_credit); + $this->pr(18, compact('credits'), + "Receipt Credit Added"); + } + + // Now find all unpaid charges + if (isset($charge_ids)) { + $lquery = array('contain' => false, + 'conditions' => array('StatementEntry.id' => $charge_ids)); + } else { + $lquery = $query; + // If we're working with a specific lease, then limit charges to it + if (!empty($lease_id)) + $lquery['conditions'][] = array('StatementEntry.lease_id' => $lease_id); + } + $lquery['order'] = 'StatementEntry.effective_date ASC'; + $charges = array(); + foreach ($this->debitTypes() AS $dtype) { + $rset = $this->reconciledSet($dtype, $lquery, true); + $entries = $rset['entries']; + $charges = array_merge($charges, $entries); + $this->pr(18, compact('dtype', 'entries'), "Outstanding Debit Entries"); + } + + // Work through all unpaid charges, applying disbursements as we go + foreach ($charges AS $charge) { + $this->pr(20, compact('charge'), + 'Process Charge'); + + $charge['balance'] = $charge['StatementEntry']['balance']; + + // Use explicit credits before using the new receipt credit + foreach ($credits AS &$credit) { + if (empty($charge['balance'])) + break; + if ($charge['balance'] < 0) + $this->INTERNAL_ERROR("Negative Charge Balance"); + + if (!isset($credit['balance'])) + $credit['balance'] = $credit['StatementEntry']['amount']; + + if (empty($credit['balance'])) + continue; + if ($credit['balance'] < 0) + $this->INTERNAL_ERROR("Negative Credit Balance"); + + $this->pr(20, compact('charge'), + 'Attempt Charge Reconciliation'); + + if (empty($credit['receipt'])) + $disbursement_account_id = $credit['StatementEntry']['account_id']; + else + $disbursement_account_id = $credit['LedgerEntry']['account_id']; + + // REVISIT : 20090811 + // Need to come up with a better strategy for handling + // concessions. For now, just restricting concessions + // to apply only towards rent will resolve the most + // predominant (or only) needed usage case. + if ($disbursement_account_id == $this->Account->concessionAccountID() && + $charge['StatementEntry']['account_id'] != $this->Account->rentAccountID()) + continue; + + // Set the disbursement amount to the maximum amount + // possible without exceeding the charge or credit balance + $disbursement_amount = min($charge['balance'], $credit['balance']); + if (!isset($credit['applied'])) + $credit['applied'] = 0; + + $credit['applied'] += $disbursement_amount; + $credit['balance'] -= $disbursement_amount; + + $this->pr(20, compact('credit'), + ($credit['balance'] > 0 ? 'Utilized' : 'Exhausted') . + (empty($credit['receipt']) ? ' Credit' : ' Receipt')); + + if (strtotime($charge['StatementEntry']['effective_date']) > + strtotime($credit['StatementEntry']['effective_date'])) + $disbursement_edate = $charge['StatementEntry']['effective_date']; + else + $disbursement_edate = $credit['StatementEntry']['effective_date']; + + if (empty($credit['receipt'])) { + // Explicit Credit + $result = $this->Transaction->addTransactionEntries + (array('include_ledger_entry' => true, + 'include_statement_entry' => true), + array('type' => 'INVOICE', + 'id' => $credit['StatementEntry']['transaction_id'], + 'account_id' => $this->Account->accountReceivableAccountID(), + 'crdr' => 'CREDIT', + 'customer_id' => $charge['StatementEntry']['customer_id'], + 'lease_id' => $charge['StatementEntry']['lease_id'], + ), + array + (array('type' => $disbursement_type, + 'effective_date' => $disbursement_edate, + 'account_id' => $credit['StatementEntry']['account_id'], + 'amount' => $disbursement_amount, + 'charge_entry_id' => $charge['StatementEntry']['id'], + ), + )); + + $ret['Disbursement'][] = $result; + if ($result['error']) + $ret['error'] = true; + } + else { + // Receipt Credit + + if (strtotime($charge['StatementEntry']['effective_date']) > + strtotime($credit['Transaction']['stamp'])) + $disbursement_edate = $charge['StatementEntry']['effective_date']; + else + $disbursement_edate = $credit['Transaction']['stamp']; + + // Add a disbursement that uses the available credit to pay the charge + $disbursement = + array('type' => $disbursement_type, + 'effective_date' => $disbursement_edate, + 'amount' => $disbursement_amount, + 'account_id' => $credit['LedgerEntry']['account_id'], + 'transaction_id' => $credit['Transaction']['id'], + 'customer_id' => $charge['StatementEntry']['customer_id'], + 'lease_id' => $charge['StatementEntry']['lease_id'], + 'charge_entry_id' => $charge['StatementEntry']['id'], + 'comment' => null, + ); + + $this->pr(20, compact('disbursement'), 'New Disbursement Entry'); + $result = $this->addStatementEntry($disbursement); + $ret['Disbursement'][] = $result; + if ($result['error']) + $ret['error'] = true; + } + + // Adjust the charge balance to reflect the new disbursement + $charge['balance'] -= $disbursement_amount; + if ($charge['balance'] < 0) + die("HOW DID WE GET A NEGATIVE CHARGE AMOUNT?"); + + if ($charge['balance'] <= 0) + $this->pr(20, 'Fully Paid Charge'); + } + // Break the $credit reference to avoid future problems + unset($credit); + } + + $this->pr(18, compact('credits'), + 'Disbursements complete'); + + // Clean up any explicit credits that have been used + foreach ($credits AS $credit) { + if (!empty($credit['receipt'])) + continue; + + if (empty($credit['applied'])) + continue; + + if ($credit['balance'] > 0) { + $this->pr(20, compact('credit'), + 'Update Credit Entry'); + + $this->id = $credit['StatementEntry']['id']; + $this->saveField('amount', $credit['balance']); + } + else { + $this->pr(20, compact('credit'), + 'Delete Exhausted Credit Entry'); + + $this->delete($credit['StatementEntry']['id'], false); + } + } + + // Check for any implicit receipt credits, converting + // into explicit credits if there is a remaining balance. + foreach ($credits AS $credit) { + if (empty($credit['receipt'])) + continue; + + if (empty($credit['balance'])) + continue; + + // See if there is an existing explicit credit + // for this transaction. + $explicit_credit = $this->find + ('first', array('contain' => false, + 'conditions' => + array(array('transaction_id' => $credit['Transaction']['id']), + array('type' => 'SURPLUS')), + )); + + if (!empty($explicit_credit)) { + // REVISIT : 20090815 + // Testing whether or not this case occurs + $this->INTERNAL_ERROR('Existing explicit credit unexpected'); + + // Since there IS an existing explicit credit, we must update + // its balance instead of creating a new one, since it has + // already been incorporated in the overall credit balance. + // If we were to create a new one, we would erroneously create + // an excess of credit available. + $this->pr(18, compact('explicit_credit', 'credit'), + 'Update existing explicit credit'); + $EC = new StatementEntry(); + $EC->id = $explicit_credit['StatementEntry']['id']; + $EC->saveField('amount', $credit['balance']); + continue; + } + + if (!empty($ret['receipt_balance'])) + $this->INTERNAL_ERROR('Only one receipt expected in assignCredits'); + + // Give caller the information necessary to create an explicit + // credit from the passed receipt, which we've not exhausted. + $this->pr(18, compact('credit'), 'Convert to explicit credit'); + $ret['receipt_balance'] = $credit['balance']; + } + + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested statement entry + */ + function stats($id = null, $query = null) { + //$this->prFunctionLevel(array('log' => 16, 'show' => 10)); + $this->prEnter(compact('id', 'query')); + + $this->queryInit($query); + unset($query['group']); + + $stats = array(); + if (isset($id)) + $query['conditions'][] = array('StatementEntry.id' => $id); + + $types = array('Charge', 'Disbursement'); + foreach ($types AS $type_index => $this_name) { + $that_name = $types[($type_index + 1) % 2]; + if ($this_name === 'Charge') { + $this_types = $this->debitTypes(); + $that_types = $this->creditTypes(); + } else { + $this_types = $this->creditTypes(); + $that_types = $this->debitTypes(); + } + + $this_query = $query; + $this_query['fields'] = array(); + $this_query['fields'][] = "SUM(StatementEntry.amount) AS total"; + $this_query['conditions'][] = array('StatementEntry.type' => $this_types); + $result = $this->find('first', $this_query); + $stats[$this_name] = $result[0]; + + $this->pr(17, compact('this_query', 'result'), $this_name.'s'); + + // Tally the different types that result in credits towards the charges + $stats[$this_name]['reconciled'] = 0; + foreach ($that_types AS $that_type) { + $lc_that_type = strtolower($that_type); + $that_query = $this_query; + $that_query['link']["{$that_name}Entry"] = array('fields' => array()); + $that_query['fields'] = array(); + if ($this_name == 'Charge') + $that_query['fields'][] = "COALESCE(SUM(${that_name}Entry.amount),0) AS $lc_that_type"; + else + $that_query['fields'][] = "COALESCE(SUM(StatementEntry.amount), 0) AS $lc_that_type"; + $that_query['conditions'][] = array("{$that_name}Entry.type" => $that_type); + $result = $this->find('first', $that_query); + $stats[$this_name] += $result[0]; + + $this->pr(17, compact('that_query', 'result'), "{$this_name}s: $that_type"); + $stats[$this_name]['reconciled'] += $stats[$this_name][$lc_that_type]; + } + + // Compute balance information for charges + $stats[$this_name]['balance'] = + $stats[$this_name]['total'] - $stats[$this_name]['reconciled']; + if (!isset($stats[$this_name]['balance'])) + $stats[$this_name]['balance'] = 0; + } + + // 'balance' is simply the difference between + // the balances of charges and disbursements + $stats['balance'] = $stats['Charge']['balance'] - $stats['Disbursement']['balance']; + if (!isset($stats['balance'])) + $stats['balance'] = 0; + + // 'account_balance' is really only relevant to + // callers that have requested charge and disbursement + // stats with respect to a particular account. + // It represents the difference between inflow + // and outflow from that account. + $stats['account_balance'] = $stats['Charge']['reconciled'] - $stats['Disbursement']['total']; + if (!isset($stats['account_balance'])) + $stats['account_balance'] = 0; + + return $this->prReturn($stats); + } + +} \ No newline at end of file diff --git a/models/tender.php b/models/tender.php new file mode 100644 index 0000000..37a127a --- /dev/null +++ b/models/tender.php @@ -0,0 +1,166 @@ + array( + 'className' => 'Transaction', + ), + 'DepositLedgerEntry' => array( + 'className' => 'LedgerEntry', + ), + 'NsfTransaction' => array( + 'className' => 'Transaction', + 'dependent' => true, + ), + ); + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: afterSave + * - Performs any work needed after the save occurs + */ + + function afterSave($created) { + // Come up with a (not necessarily unique) name for the tender. + // For checks & money orders, this will be based on the check + // number. For other types of tender, we'll just use the + // generic name of the tender type, and the tender ID + + // Determine our tender type, and set the ID of that model + $this->TenderType->id = $this->field('tender_type_id'); + + // REVISIT : 20090810 + // The only tender expected to have no tender type + // is our special "Closing" tender. + if (empty($this->TenderType->id)) + $newname = 'Closing'; + else { + $newname = $this->TenderType->field('name'); + $naming_field = $this->TenderType->field('naming_field'); + if (!empty($naming_field)) + $newname .= ' #' . $this->field($naming_field); + } + + if ($newname !== $this->field('name')) + $this->saveField('name', $newname); + + return parent::afterSave($created); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: beforeDelete + * - Performs any work needed before the delete occurs + */ + + function beforeDelete($cascade = true) { + // REVISIT : 20090814 + // Experimental, and incomplete mechanism to protect + // against trying to delete data that shouldn't be deleted. + + $deposit_id = $this->field('deposit_transaction_id'); + pr(compact('deposit_id')); + // If this tender has already been deposited, it would + // be a rats nest to figure out how to delete this tender. + if (!empty($deposit_id)) + return false; + + return parent::beforeDelete($cascade); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyTender + * - Verifies consistenty of new tender data + * (not in a pre-existing tender) + */ + function verifyTender($tender) { + $this->prFunctionLevel(10); + $this->prEnter(compact('tender')); + + if (empty($tender['tender_type_id'])) { + return $this->prReturn(false); + } + + return $this->prReturn(true); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addTender + * - Inserts new Tender into the database + */ + + function addTender($tender) { + $this->prEnter(compact('tender')); + + $ret = array('data' => $tender); + if (!$this->verifyTender($tender)) + return $this->prReturn(array('error' => true) + $ret); + + $this->create(); + if (!$this->save($tender)) + return $this->prReturn(array('error' => true) + $ret); + + $ret['tender_id'] = $this->id; + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: nsf + * - Flags the ledger entry as having insufficient funds + */ + + function nsf($id, $stamp = null, $comment = null) { + $this->prEnter(compact('id', 'stamp', 'comment')); + + // Get information about this NSF item. + $this->id = $id; + $tender = $this->find + ('first', array + ('contain' => + array('LedgerEntry', + 'DepositTransaction', + 'DepositLedgerEntry', + 'NsfTransaction'), + )); + $this->pr(20, compact('tender')); + + if (!empty($tender['NsfTransaction']['id'])) + die("Item has already been set as NSF"); + + if (empty($tender['DepositTransaction']['id'])) + die("Item has not been deposited yet"); + + $tender['Transaction'] = $tender['DepositTransaction']; + unset($tender['DepositTransaction']); + unset($tender['NsfTransaction']); + + $T = new Transaction(); + $result = $T->addNsf($tender, $stamp, $comment); + if (empty($result['error'])) { + // Flag the tender as NSF, using the items created from addNsf + $this->id = $id; + $this->saveField('nsf_transaction_id', $result['nsf_transaction_id']); + $this->saveField('nsf_ledger_entry_id', $result['nsf_ledger_entry_id']); + } + + return $this->prReturn($result); + } + + +} +?> \ No newline at end of file diff --git a/models/tender_type.php b/models/tender_type.php new file mode 100644 index 0000000..5d70cf0 --- /dev/null +++ b/models/tender_type.php @@ -0,0 +1,115 @@ +cacheQueries = true; + $item = $this->find('first', array + ('contain' => false, + 'conditions' => array('TenderType.id' => $id), + )); + $this->cacheQueries = false; + return $item['TenderType']['account_id']; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: paymentTypes + * - Returns an array of types that can be used for payments + */ + + function paymentTypes($query = null) { + $this->queryInit($query); + $query['order'][] = 'name'; + + $this->cacheQueries = true; + $types = $this->find('all', $query); + $this->cacheQueries = false; + + return $types; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: paymentTypes + * - Returns an array of types that can be deposited + */ + + function depositTypes($query = null) { + $this->queryInit($query); + $query['order'][] = 'name'; + $query['conditions'][] = array('tillable' => true); + + $this->cacheQueries = true; + $types = $this->find('all', $query); + $this->cacheQueries = false; + + // Rearrange to be of the form (id => name) + $result = array(); + foreach ($types AS $type) + $result[$type['TenderType']['id']] = $type['TenderType']['name']; + + return $result; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: defaultPaymentType + * - Returns the ID of the default payment type + */ + + function defaultPaymentType() { + return $this->nameToID('Check'); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns the stats for the given tender type + */ + + function stats($id = null, $query = null) { + if (!$id) + return null; + + $this->queryInit($query); + + if (!isset($query['link']['Tender'])) + $query['link']['Tender'] = array('fields' => array()); + if (!isset($query['link']['Tender']['LedgerEntry'])) + $query['link']['Tender']['LedgerEntry'] = array('fields' => array()); + + $query['fields'][] = "SUM(COALESCE(LedgerEntry.amount,0)) AS 'total'"; + $query['fields'][] = "SUM(IF(deposit_transaction_id IS NULL, COALESCE(LedgerEntry.amount,0), 0)) AS 'undeposited'"; + $query['fields'][] = "SUM(IF(deposit_transaction_id IS NULL, 0, COALESCE(LedgerEntry.amount,0))) AS 'deposited'"; + $query['fields'][] = "SUM(IF(nsf_transaction_id IS NULL, 0, COALESCE(LedgerEntry.amount,0))) AS 'nsf'"; + + $this->id = $id; + $stats = $this->find('first', $query); + return $stats[0]; + } + +} \ No newline at end of file diff --git a/models/transaction.php b/models/transaction.php new file mode 100644 index 0000000..de8923a --- /dev/null +++ b/models/transaction.php @@ -0,0 +1,1344 @@ + array( + 'className' => 'Tender', + 'foreignKey' => 'nsf_transaction_id', + ), + ); + + var $hasMany = array( + 'LedgerEntry' => array( + 'dependent' => true, + ), + 'StatementEntry' => array( + 'dependent' => true, + ), + + 'DepositTender' => array( + 'className' => 'Tender', + 'foreignKey' => 'deposit_transaction_id', + ), + + 'Charge' => array( + 'className' => 'StatementEntry', + 'conditions' => array('Charge.type' => 'CHARGE') + ), + + 'Disbursement' => array( + 'className' => 'StatementEntry', + 'conditions' => array('Disbursement.type' => 'DISBURSEMENT') + ), + + 'Debit' => array( + 'className' => 'LedgerEntry', + 'conditions' => array('Debit.crdr' => 'DEBIT') + ), + + 'Credit' => array( + 'className' => 'LedgerEntry', + 'conditions' => array('Credit.crdr' => 'CREDIT') + ), + + ); + + + //var $default_log_level = array('log' => 30, 'show' => 15); + //var $max_log_level = 10; + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addInvoice + * - Adds a new invoice invoice + */ + + function addInvoice($data, $customer_id, $lease_id = null) { + $this->prEnter(compact('data', 'customer_id', 'lease_id')); + + // Set up control parameters + $data += array('control' => array()); + $data['control'] += + array('assign' => true, + 'include_ledger_entry' => true, + 'include_statement_entry' => true, + ); + + // Establish the transaction as an invoice + $data['Transaction'] += + array('type' => 'INVOICE', + 'crdr' => 'DEBIT', + 'account_id' => $this->Account->accountReceivableAccountID(), + 'customer_id' => $customer_id, + 'lease_id' => $lease_id, + ); + + // Go through the statement entries and flag as charges + foreach ($data['Entry'] AS &$entry) + $entry += array('type' => 'CHARGE', + ); + + $ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']); + if (isset($ids['transaction_id'])) + $ids['invoice_id'] = $ids['transaction_id']; + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addReceipt + * - Adds a new receipt + */ + + function addReceipt($data, $customer_id, $lease_id = null) { + $this->prEnter(compact('data', 'customer_id', 'lease_id')); + + // Set up control parameters + $data += array('control' => array()); + $data['control'] += + array('assign' => true, + 'assign_receipt' => true, + 'include_ledger_entry' => true, + 'include_statement_entry' => false, + ); + + // Establish the transaction as a receipt + $data['Transaction'] += + array('type' => 'RECEIPT', + 'crdr' => 'CREDIT', + 'account_id' => $this->Account->accountReceivableAccountID(), + 'customer_id' => $customer_id, + 'lease_id' => $lease_id, + ); + + // Go through the statement entries, making sure the tender + // is recorded into the correct account, and then performing + // an auto-deposit if necessary. + $deposit = array(); + foreach ($data['Entry'] AS &$entry) { + if (empty($entry['Tender']['tender_type_id'])) + continue; + + $ttype = $this->LedgerEntry->Tender->TenderType->find + ('first', array('contain' => false, + 'conditions' => + array('id' => $entry['Tender']['tender_type_id']))); + $ttype = $ttype['TenderType']; + + // Set the account for posting. + $entry += array('account_id' => $ttype['account_id']); + +/* // Check for auto deposit */ +/* if (!empty($ttype['auto_deposit'])) { */ +/* $deposit[] = array('id' => 0, */ +/* 'account_id' => $ttype['deposit_account_id']); */ +/* } */ + } + unset($entry); // prevent trouble since $entry is reference + + $ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']); + if (isset($ids['transaction_id'])) + $ids['receipt_id'] = $ids['transaction_id']; + if (!empty($ids['error'])) + return $this->prReturn(array('error' => true) + $ids); + + $tender_ids = array(); + foreach ($ids['entries'] AS $entry) { + $entry1 = $entry['DoubleEntry']['Entry1']; + if (!empty($entry1['Tender']['tender_id'])) + $tender_ids[] = $entry1['Tender']['tender_id']; + } + + $ids = $this->_autoDeposit($tender_ids, $ids); + + return $this->prReturn($ids); + } + + // REVISIT : 20090817 + // Delete after rolling up the old items + function _autoDeposit($tender_ids, $ids) { + $deposit_tenders = $this->LedgerEntry->Tender->find + ('all', array('contain' => array('TenderType' => array('fields' => array()), + 'LedgerEntry' => array('fields' => array()), + ), + 'fields' => array('TenderType.deposit_account_id', + 'TenderType.account_id', + 'CONCAT("CREDIT") AS crdr', + 'CONCAT("Auto Deposit") AS comment', + 'SUM(LedgerEntry.amount) AS amount'), + 'conditions' => array('Tender.id' => $tender_ids, + 'TenderType.auto_deposit' => true, + ), + 'group' => 'TenderType.deposit_account_id', + )); + + if (!empty($deposit_tenders)) { + foreach ($deposit_tenders AS &$tender) + $tender = $tender[0] + array_diff_key($tender['TenderType'], array('id'=>1)); + + $this->pr(10, compact('tender_ids', 'deposit_tenders')); + + // REVISIT : 20090817 + // Multiple tenders could result in deposits to more than one + // account. We're already mucking with things by having a + // ledger entry that's not involved with the account_id of the + // transaction. We could handle this by not using the helper + // _splitEntries function, and just building or individual + // entries right here (which we should probably do anyway). + // However, I'm ignoring the issue for now... + if (count($deposit_tenders) > 1) + $this->INTERNAL_ERROR("Only expecting one tender type"); + + $deposit_ids = $this->addTransactionEntries + (array('include_ledger_entry' => true, + 'include_statement_entry' => false, + ), + + array('id' => $ids['transaction_id'], + // REVISIT : 20090817 + // This is an awful cheat, and we're going to + // get burned from it someday. + 'type' => 'DEPOSIT', + 'crdr' => 'DEBIT', + 'account_id' => $deposit_tenders[0]['deposit_account_id'], + ), + + $deposit_tenders); + + $ids['deposit'] = $deposit_ids; + if (!empty($deposit_ids['error'])) + return $this->prReturn(array('error' => true) + $ids); + + if (!empty($tender_ids)) { + $entry_id = $deposit_ids['entries'][0]['DoubleEntry']['Entry2']['ledger_entry_id']; + $this->pr(10, compact('tender_ids', 'entry_id')); + $this->LedgerEntry->Tender->updateAll + (array('Tender.deposit_transaction_id' => $ids['transaction_id'], + 'Tender.deposit_ledger_entry_id' => $entry_id), + array('Tender.id' => $tender_ids) + ); + } + } + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addWaiver + * - Adds a new waiver + */ + + function addWaiver($data, $charge_id, $customer_id, $lease_id = null) { + $this->prEnter(compact('data', 'charge_id', 'customer_id', 'lease_id')); + + if (count($data['Entry']) != 1) + $this->INTERNAL_ERROR("Should be one Entry for addWaiver"); + + // No assignment of credits, as we'll manually assign + // using charge_entry_id as part of the entry (below). + $data += array('control' => array()); + $data['control'] += + array('assign' => false, + 'include_ledger_entry' => true, + 'include_statement_entry' => true, + ); + + // Just make sure the disbursement(s) are marked as waivers + // and that they go to cover the specific charge. + $data['Entry'][0] += + array('type' => 'WAIVER', + 'account_id' => $this->Account->waiverAccountID(), + 'charge_entry_id' => $charge_id); + + // In all other respects this is just a receipt. + $ids = $this->addReceipt($data, $customer_id, $lease_id); + if (isset($ids['transaction_id'])) + $ids['waiver_id'] = $ids['transaction_id']; + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addWriteOff + * - Adds a new write off of bad debt + */ + + function addWriteOff($data, $customer_id, $lease_id = null) { + $this->prEnter(compact('data', 'customer_id', 'lease_id')); + + if (count($data['Entry']) != 1) + $this->INTERNAL_ERROR("Should be one Entry for addWriteOff"); + + // Just make sure the disbursement(s) are marked as write offs + // and that the write-off account is used for the charge. + $data['Entry'][0] += + array('type' => 'WRITEOFF', + 'account_id' => $this->Account->badDebtAccountID()); + + // In all other respects this is just a receipt. + $ids = $this->addReceipt($data, $customer_id, $lease_id); + if (isset($ids['transaction_id'])) + $ids['writeoff_id'] = $ids['transaction_id']; + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addDeposit + * - Adds a new bank deposit + */ + + function addDeposit($data, $account_id) { + $this->prEnter(compact('data', 'account_id')); + + // Set up control parameters + $data += array('control' => array()); + $data['control'] += + array('assign' => false, + 'include_ledger_entry' => true, + 'include_statement_entry' => false, + 'update_tender' => true, + ); + + // Establish the transaction as a deposit + $data['Transaction'] += + array('type' => 'DEPOSIT', + 'crdr' => 'DEBIT', + 'account_id' => $account_id, + 'customer_id' => null, + 'lease_id' => null, + ); + + // Save the list of IDs, so that we can mark their + // deposit transaction after it has been created. + $tender_ids = array_map(create_function('$item', 'return $item["tender_id"];'), + $data['Entry']); + + // Go through the statement entries and re-group by account id + $group = array(); + $tender_groups = array(); + foreach ($data['Entry'] AS $entry) { + if (!isset($group[$entry['account_id']])) + $group[$entry['account_id']] = + array('account_id' => $entry['account_id'], + 'amount' => 0); + $group[$entry['account_id']]['amount'] += $entry['amount']; + $tender_groups[$entry['account_id']][] = $entry['tender_id']; + } + $data['Entry'] = $group; + + $ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']); + if (isset($ids['transaction_id'])) + $ids['deposit_id'] = $ids['transaction_id']; + + if (!empty($ids['deposit_id']) && !empty($control['update_tender'])) { + foreach ($tender_groups AS $group => $tender_ids) { + $entry_id = $ids['entries'][$group]['DoubleEntry']['Entry2']['ledger_entry_id']; + $this->pr(10, compact('group', 'tender_ids', 'entry_id')); + $this->LedgerEntry->Tender->updateAll + (array('Tender.deposit_transaction_id' => $ids['deposit_id'], + 'Tender.deposit_ledger_entry_id' => $entry_id), + array('Tender.id' => $tender_ids) + ); + } + } + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addClose + * - Adds a new transaction for closing ledgers + */ + + function addClose($data) { + $this->prEnter(compact('data')); + + // Set up control parameters + $data += array('control' => array()); + $data['control'] += + array('assign' => false, + 'include_ledger_entry' => true, + 'include_statement_entry' => false, + 'allow_no_entries' => true, + ); + + // Establish the transaction as a close + $data['Transaction'] += + array('type' => 'CLOSE', + 'crdr' => null, + 'account_id' => null, + 'customer_id' => null, + 'lease_id' => null, + ); + + $ledger_ids = array(); + $data['Entry'] = array(); + foreach ($data['Ledger'] AS $ledger) { + $ledger_id = $ledger['old_ledger_id']; + $new_ledger_id = $ledger['new_ledger_id']; + $amount = $ledger['amount']; + $account_id = $this->Account->Ledger->accountID($ledger_id); + $crdr = strtoupper($this->Account->fundamentalOpposite($account_id)); + $comment = "Ledger Carry Forward (c/f)"; + + // Save the ledger ID for later, to mark it as closed + $ledger_ids[] = $ledger_id; + + // No need to generate ledger entries if there is no balance + if (empty($ledger['amount']) || $ledger['amount'] == 0) + continue; + + // Add an entry to carry the ledger balance forward + $data['Entry'][] = compact('account_id', 'ledger_id', 'new_ledger_id', + 'crdr', 'amount', 'comment'); + } + unset($data['Ledger']); + + // Add the transaction and carry forward balances + $ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']); + if (isset($ids['transaction_id'])) + $ids['close_id'] = $ids['transaction_id']; + + // Mark the older ledgers as closed + if (!empty($ids['close_id'])) { + $this->LedgerEntry->Ledger->updateAll + (array('Ledger.close_transaction_id' => $ids['close_id']), + array('Ledger.id' => $ledger_ids) + ); + } + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addRefund + * - Adds a new refund + */ + + function addRefund($data, $customer_id, $lease_id = null) { + $this->prEnter(compact('data', 'customer_id', 'lease_id')); + + // Set up control parameters + $data += array('control' => array()); + $data['control'] += + array('assign' => true, + ); + + // Establish the transaction as a Refund. This is just like a + // Payment, except instead of paying out of the account payable, + // it comes from the customer credit in the account receivable. + // Someday, perhaps we'll just issue a Credit Note or similar, + // but for now, a refund means it's time to actually PAY. + $data['Transaction'] += + array('account_id' => $this->Account->accountReceivableAccountID()); + + // Also, to make it clear to the user, we flag as a REFUND + // even though that type works and operates just as PAYMENT + foreach ($data['Entry'] AS &$entry) + $entry += array('type' => 'REFUND'); + + $ids = $this->addPayment($data, $customer_id, $lease_id); + if (isset($ids['transaction_id'])) + $ids['refund_id'] = $ids['transaction_id']; + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addPayment + * - Adds a new payment transaction, which is money outflow + */ + + function addPayment($data, $customer_id, $lease_id = null) { + $this->prEnter(compact('data', 'customer_id', 'lease_id')); + + // Set up control parameters + $data += array('control' => array()); + $data['control'] += + array('assign' => false, + 'include_ledger_entry' => true, + 'include_statement_entry' => true, + ); + + // Establish the transaction as an payment + $data['Transaction'] += + array('type' => 'PAYMENT', + 'crdr' => 'DEBIT', + 'account_id' => $this->Account->accountPayableAccountID(), + 'customer_id' => $customer_id, + 'lease_id' => $lease_id, + ); + + // Go through the statement entries and flag as payments + foreach ($data['Entry'] AS &$entry) + $entry += array('type' => 'PAYMENT', + ); + + $ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']); + if (isset($ids['transaction_id'])) + $ids['payment_id'] = $ids['transaction_id']; + + return $this->prReturn($ids); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyTransaction + * - Verifies consistenty of new transaction data + * (not in a pre-existing transaction) + */ + function verifyTransaction($transaction, $entries) { + //$this->prFunctionLevel(10); + $this->prEnter(compact('transaction', 'entries')); + + // Verify required Transaction data is present + if (empty($transaction['type']) || + ($transaction['type'] != 'CLOSE' + && (empty($transaction['account_id']) || + empty($transaction['crdr']))) || + (in_array($transaction['type'], array('INVOICE', 'RECEIPT')) + && empty($transaction['customer_id'])) + ) { + return $this->prReturn(false); + } + + // Verify all entries + foreach ($entries AS $entry) { + // Ensure these items are null'ed out so we don't + // accidentally pick up stale data. + $le1 = $le1_tender = $le2 = $se = null; + extract($entry); + if (!empty($le1) && !empty($le2) && + !$this->LedgerEntry->DoubleEntry->verifyDoubleEntry($le1, $le2, $le1_tender)) { + return $this->prReturn(false); + } + if (!empty($se) && + !$this->StatementEntry->verifyStatementEntry($se)) { + return $this->prReturn(false); + } + } + + return $this->prReturn(true); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addTransaction + * - Adds a new transaction, and the appropriate ledger and statement + * entries, as layed out in the $data['Entry'] array. The array is + * overloaded, since it is used to create both ledger _and_ statement + * entries. + */ + + function addTransaction($control, $transaction, $entries) { + $this->prEnter(compact('control', 'transaction', 'entries')); + + $result = $this->_splitEntries($control, $transaction, $entries); + if (!empty($result['error'])) + return $this->prReturn(array('error' => true)); + + // Make use of the work done by splitEntries + $transaction = $this->filter_null($transaction) + $result['transaction']; + $entries = $result['entries']; + extract($result['vars']); + + $this->pr(20, compact('transaction', 'entries')); + + // Move forward, verifying and saving everything. + $ret = array('data' => $transaction); + if (!$this->verifyTransaction($transaction, $entries)) + return $this->prReturn(array('error' => true) + $ret); + + // Save transaction to the database + $this->create(); + if (!$this->save($transaction)) + return $this->prReturn(array('error' => true) + $ret); + $ret['transaction_id'] = $transaction['id'] = $this->id; + + // Add the entries + $ret += $this->addTransactionEntries($control, $transaction, $entries, false); + + // If the caller requests 'assign'=>true, they really + // want to do a credit assignment, and _then_ create + // an explicit credit with any leftover. If an array + // is specified, they get full control of the order. + if (empty($control['assign'])) + $assign_ops = array(); + elseif (is_array($control['assign'])) + $assign_ops = $control['assign']; + elseif (is_bool($control['assign'])) + $assign_ops = (empty($control['assign_receipt']) + ? array('assign') + : array('assign', 'create')); + else + $this->INTERNAL_ERROR('Invalid control[assign] parameter'); + + $this->pr(17, compact('assign_ops'), 'Credit operations'); + + // Go through the requested assignment mechanisms + foreach ($assign_ops AS $method) { + if (!empty($ret['error'])) + break; + + $this->pr(17, compact('method'), 'Handling credits'); + + if ($method === 'assign') { + $result = $this->StatementEntry->assignCredits + (null, + (empty($control['assign_receipt']) ? null + : $ret['transaction_id']), + null, + $assign_disbursement_type, + $transaction['customer_id'], + $transaction['lease_id'] + ); + } + elseif ($method === 'create' || is_numeric($method)) { + if (is_numeric($method)) + $credit_amount = $method; + else { + $stats = $this->stats($transaction['id']); + $credit_amount = $stats['undisbursed']; + } + + if ($credit_amount < 0) + $this->INTERNAL_ERROR('Receipt has negative undisbursed balance'); + + if (empty($credit_amount)) + continue; + + $result = $this->addTransactionEntries + (array('include_ledger_entry' => true, + 'include_statement_entry' => true), + array('crdr' => 'DEBIT') + $transaction, + array(array('type' => 'SURPLUS', + 'account_id' => $this->Account->customerCreditAccountID(), + 'amount' => $credit_amount, + ), + )); + } + else + $this->INTERNAL_ERROR('Invalid assign method'); + + $ret['credit'][$method] = $result; + if (!empty($result['error'])) + $ret['error'] = true; + } + + if (!empty($transaction['customer_id'])) { + $this->Customer->update($transaction['customer_id']); + } + return $this->prReturn($ret); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addTransactionEntries + * - Largely a helper function to addTransaction, this function is + * responsible for adding ledger/statement entries to an existing + * transaction. If needed, this function can also be called outside + * of addTransaction, although it's not clear where that would be + * appropriate, since transactions are really snapshots of some + * event, and shouldn't be mucked with after creation. + */ + + function addTransactionEntries($control, $transaction, $entries, $split = true) { + $this->prEnter(compact('control', 'transaction', 'entries', 'split')); + + // Verify that we have a transaction + if (empty($transaction['id'])) + return $this->prReturn(array('error' => true)); + + // If the entries are not already split, do so now. + if ($split) { + $result = $this->_splitEntries($control, $transaction, $entries); + if (!empty($result['error'])) + return $this->prReturn(array('error' => true)); + + // Make use of the work done by splitEntries + $transaction = $this->filter_null($transaction) + $result['transaction']; + $entries = $result['entries']; + extract($result['vars']); + +/* // Verify the entries */ +/* $ret = array(); */ +/* if (!$this->verifyTransaction($transaction, $entries)) */ +/* return $this->prReturn(array('error' => true) + $ret); */ + } + + $this->id = $transaction['id']; + $transaction['stamp'] = $this->field('stamp'); + $transaction['customer_id'] = $this->field('customer_id'); + + // Set up our return array + $ret = array(); + $ret['entries'] = array(); + $ret['error'] = false; + + // Go through the entries + foreach ($entries AS $e_index => &$entry) { + // Ensure these items are null'ed out so we don't + // accidentally pick up stale data. + $le1 = $le1_tender = $le2 = $se = null; + extract($entry); + + if (!empty($le1) && !empty($le2)) { + $le1['transaction_id'] = $le2['transaction_id'] = $transaction['id']; + if (isset($le1_tender)) + $le1_tender['customer_id'] = $transaction['customer_id']; + $result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender); + $ret['entries'][$e_index]['DoubleEntry'] = $result; + if ($result['error']) { + $ret['error'] = true; + continue; + } + } + + if (!empty($se)) { + $se['transaction_id'] = $transaction['id']; + if (empty($se['effective_date'])) + $se['effective_date'] = $transaction['stamp']; + $result = $this->StatementEntry->addStatementEntry($se); + $ret['entries'][$e_index]['StatementEntry'] = $result; + if ($result['error']) { + $ret['error'] = true; + continue; + } + } + } + + return $this->prReturn($ret); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: _splitEntries + * - An internal helper function capable of splitting an array of + * combined ledger/statement entries into their indivdual entry + * components (2 ledger entries, and a statement entry), based + * on the typical requirements. Any custom split will have to + * be done outside of this function. + */ + + function _splitEntries($control, $transaction, $entries) { + $this->prEnter(compact('control', 'transaction', 'entries')); + + // Verify that we have a transaction and entries + if (empty($transaction) || + (empty($entries) && empty($control['allow_no_entries']))) + return $this->prReturn(array('error' => true)); + + // set ledger ID as the current ledger of the specified account + if (empty($transaction['ledger_id'])) + $transaction['ledger_id'] = + $this->Account->currentLedgerID($transaction['account_id']); + + // Automatically figure out the customer if we have the lease + if (!empty($transaction['lease_id']) && empty($transaction['customer_id'])) { + $L = new Lease(); + $L->id = $transaction['lease_id']; + $transaction['customer_id'] = $L->field('customer_id'); + } + + if (!empty($transaction['account_id'])) { + if (empty($transaction['ledger_id'])) + $transaction['ledger_id'] = + $this->Account->currentLedgerID($transaction['account_id']); + + if (empty($transaction['crdr'])) + $transaction['crdr'] = strtoupper($this->Account->fundamentalType + ($transaction['account_id'])); + } + + // Some transactions do not have their statement entries + // generated directly as part of the transaction, but are + // created in the final steps during the reconciliation + // phase by the assignCredits function. Keep track of + // what type the statement entries _would_ have been, so + // that the assignCredits function can do the same. + $assign_disbursement_type = null; + + // Break each entry out of the combined statement/ledger entry + // and into individual entries appropriate for saving. While + // we're at it, calculate the transaction total as well. + $transaction['amount'] = 0; + foreach ($entries AS &$entry) { + // Ensure these items are null'ed out so we don't + // accidentally pick up stale data. + $le1 = $le1_tender = $le2 = $se = null; + + // Really, data should be sanitized at the controller, + // and not here. However, it's a one stop cleanup. + $entry['amount'] = str_replace('$', '', $entry['amount']); + + // Set up our comments, possibly using the default 'comment' field + if (empty($entry['ledger_entry_comment'])) { + if ($transaction['type'] != 'INVOICE' && !empty($entry['comment'])) + $entry['ledger_entry_comment'] = $entry['comment']; + else + $entry['ledger_entry_comment'] = null; + } + if (empty($entry['statement_entry_comment'])) { + if ($transaction['type'] == 'INVOICE' && !empty($entry['comment'])) + $entry['statement_entry_comment'] = $entry['comment']; + else + $entry['statement_entry_comment'] = null; + } + + if (empty($entry['crdr']) && !empty($transaction['crdr'])) + $entry['crdr'] = strtoupper($this->Account->fundamentalOpposite + ($transaction['crdr'])); + + // Priority goes to settings defined in $entry, but + // use the control information as defaults. + $entry += $control; + + if (!empty($entry['include_ledger_entry'])) { + // Create one half of the Double Ledger Entry (and the Tender) + $le1 = + array_intersect_key($entry, + array_flip(array('ledger_id', 'account_id', 'crdr', 'amount'))); + $le1['comment'] = $entry['ledger_entry_comment']; + $le1_tender = isset($entry['Tender']) ? $entry['Tender'] : null; + + // Create the second half of the Double Ledger Entry + if ($transaction['type'] == 'CLOSE') { + $le2 = + array_intersect_key($entry, + array_flip(array('account_id', 'amount'))); + $le2['ledger_id'] = $entry['new_ledger_id']; + $le2['crdr'] = strtoupper($this->Account->fundamentalType($le2['account_id'])); + $le2['comment'] = "Ledger Balance Forward (b/f)"; + } + else { + $le2 = + array_intersect_key($entry, + array_flip(array('amount'))) + + array_intersect_key($transaction, + array_flip(array('ledger_id', 'account_id', 'crdr'))); + } + + if ($entry['amount'] < 0 && !empty($entry['force_positive'])) { + $le1['amount'] *= -1; + $le2['amount'] *= -1; + $entry += array('swap_crdr' => true); + } + + if (!empty($entry['swap_crdr'])) + list($le1['crdr'], $le2['crdr']) = array($le2['crdr'], $le1['crdr']); + } + else + $le1 = $le1_tender = $le2 = null; + + // Now that the ledger entries are in place, respect the 'negative' flag + if (!empty($entry['negative'])) + $entry['amount'] *= -1; + + if (!empty($entry['include_statement_entry'])) { + // Create the statement entry + $se = + array_intersect_key($entry, + array_flip(array('type', 'account_id', 'amount', + 'effective_date', 'through_date', 'due_date', + 'customer_id', 'lease_id', + 'charge_entry_id'))) + + array_intersect_key($transaction, + array_flip(array('customer_id', 'lease_id'))); + $se['comment'] = $entry['statement_entry_comment']; + } + else { + if (!empty($entry['assign']) && + !empty($entry['type']) && + empty($entry['charge_entry_id'])) { + if (empty($assign_disbursement_type)) + $assign_disbursement_type = $entry['type']; + elseif ($entry['type'] != $assign_disbursement_type) + $this->INTERNAL_ERROR('Multiple disbursement types for this transaction'); + } + + $se = null; + } + + // Add entry amount into the transaction total + $transaction['amount'] += $entry['amount']; + + // Replace combined entry with our new individual entries + $entry = compact('le1', 'le1_tender', 'le2', 'se'); + } + + return $this->prReturn(compact('transaction', 'entries') + + array('vars' => compact('assign_disbursement_type')) + + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addNsf + * - Adds NSF transaction + */ + + function addNsf($tender, $stamp = null, $comment = null) { + $this->prEnter(compact('tender', 'stamp', 'comment')); + + $ret = array(); + + // Enter the NSF + // This is the transaction pulling money from the bank account + // and recording it in the NSF account. It has nothing to do + // with the customer statement (charges, disbursements, credits, etc). + $bounce_result = $this->addDeposit + (array('control' => + // This is not a "normal" deposit, so we don't + // want to update the tender deposit transaction id + // (it already has the correct one). + array('update_tender' => false), + + 'Transaction' => + array('stamp' => $stamp, + 'type' => 'WITHDRAWAL', + 'crdr' => 'CREDIT'), + + 'Entry' => + array(array('tender_id' => null, + 'account_id' => $this->Account->nsfAccountID(), + 'amount' => $tender['LedgerEntry']['amount'], + ))), + $tender['DepositLedgerEntry']['account_id']); + + $this->pr(20, compact('bounce_result')); + $ret['bounce'] = $bounce_result; + if ($bounce_result['error']) + return $this->prReturn(array('error' => true) + $ret); + + // Since we may have saved the nsf transaction with a null + // timestamp, query it back out of the database to find out + // what timestamp was _really_ specified, for later use. + $bounce = $this->find + ('first', array('contain' => false, 'id' => $bounce_result['transaction_id'])); + $this->pr(20, compact('bounce')); + $stamp = $bounce['Transaction']['stamp']; + + // OK, now move into customer realm, finding all statement + // entries that were affected by the bad payment (tender). + $nsf_ledger_entry = $this->LedgerEntry->find + ('first', array + ('contain' => array('Transaction' => + array(//'fields' => array(), + 'StatementEntry' => + array(//'fields' => array(), + ), + ), + ), + 'conditions' => array('LedgerEntry.id' => $tender['LedgerEntry']['id']), + )); + + $this->pr(20, compact('nsf_ledger_entry')); + if (!$nsf_ledger_entry) + return $this->prReturn(array('error' => true) + $ret); + + // Build a transaction to adjust all of the statement entries + $rollback = + array('control' => + array('assign' => false, + 'include_ledger_entry' => false, + 'include_statement_entry' => true, + ), + + 'Transaction' => + array('stamp' => $stamp, + 'type' => 'RECEIPT', + 'crdr' => 'CREDIT', + 'account_id' => $this->Account->nsfAccountID(), + 'customer_id' => $tender['Tender']['customer_id'], + 'comment' => $comment, + ), + + 'Entry' => array()); + + $rollback['Transaction']['amount'] = 0; + foreach ($nsf_ledger_entry['Transaction']['StatementEntry'] AS $disbursement) { + if ($disbursement['type'] === 'SURPLUS') { + $disbursement['type'] = 'VOID'; + $this->StatementEntry->id = $disbursement['id']; + $this->StatementEntry->saveField('type', $disbursement['type']); + } + else { + $rollback['Entry'][] = + array('type' => $disbursement['type'], + 'amount' => -1 * $disbursement['amount'], + 'account_id' => $this->Account->nsfAccountID(), + 'customer_id' => $disbursement['customer_id'], + 'lease_id' => $disbursement['lease_id'], + 'charge_entry_id' => $disbursement['charge_entry_id'], + ); + $rollback['Transaction']['amount'] += $disbursement['amount']; + } + } + + // Add the sole ledger entry for this transaction. If there + // is not a transaction amount, then there is no point in + // recording a ledger entry of $0.00 + if (!empty($rollback['Transaction']['amount'])) { + $rollback['Entry'][] = + array('include_ledger_entry' => true, + 'include_statement_entry' => false, + 'amount' => $rollback['Transaction']['amount'], + 'account_id' => $this->Account->accountReceivableAccountID(), + ); + + // Set the transaction amount to be negative + $rollback['Transaction']['amount'] *= -1; + } + + // Record the transaction, which will un-pay previously paid + // charges, void any credits, and other similar work. + if (count($rollback['Entry'])) { + $rollback_result = $this->addTransaction($rollback['control'], + $rollback['Transaction'], + $rollback['Entry']); + $this->pr(20, compact('rollback', 'rollback_result')); + $ret['rollback'] = $rollback_result; + if ($rollback_result['error']) + return $this->prReturn(array('error' => true) + $ret); + } + + // Add NSF Charge + $charge_result = $this->addInvoice + (array('Transaction' => compact('stamp'), + + 'Entry' => + array + (array('account_id' => $this->Account->nsfChargeAccountID(), + 'effective_date' => $stamp, + // REVISIT : 20090730 + // BAD, BAD, BAD... who would actually + // hardcode a value like this???? ;-) + 'amount' => 35, + 'comment' => "NSF: " . $tender['Tender']['name'], + ), + ), + ), + $tender['Tender']['customer_id']); + + $this->pr(20, compact('charge_result')); + $ret['charge'] = $charge_result; + if ($charge_result['error']) + return $this->prReturn(array('error' => true) + $ret); + + if (!empty($ret['rollback'])) { + foreach ($ret['rollback']['entries'] AS $rentry) { + if (!empty($rentry['DoubleEntry'])) { + if (!empty($rentry['DoubleEntry']['error'])) + continue; + + foreach (array('Entry1', 'Entry2') AS $n) { + $entry = $rentry['DoubleEntry'][$n]; + if ($entry['data']['account_id'] == $this->Account->nsfAccountID()) { + if (!empty($ret['nsf_ledger_entry_id'])) + $this->INTERNAL_ERROR("More than one NSF LE ID"); + + $ret['nsf_ledger_entry_id'] = $entry['ledger_entry_id']; + } + } + } + } + } + if (empty($ret['rollback']['error']) && empty($ret['nsf_ledger_entry_id'])) { + //$this->INTERNAL_ERROR("NSF LE ID not found under rollback entries"); + // Actually, this can happen if an item is NSF without having ever + // been applied to any charges. + $ret['nsf_ledger_entry_id'] = null; + } + + $ret['nsf_transaction_id'] = $ret['bounce']['transaction_id']; + return $this->prReturn($ret + array('error' => false)); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addReversal + * - Adds a new charge reversal + */ + + function addReversal($charge, $stamp = null, $comment = null) { + $this->prEnter(compact('charge', 'stamp', 'comment')); + + $ret = array(); + + // Finding all statement entries affected by reversing this charge. + $disb_entries = $this->StatementEntry->find + ('first', array + ('contain' => array('DisbursementEntry' => + array(//'fields' => array(), + ), + ), + 'conditions' => array('StatementEntry.id' => $charge['StatementEntry']['id']), + )); + + $this->pr(20, compact('disb_entries')); + if (!$disb_entries) + return $this->prReturn(array('error' => true) + $ret); + + // Build a transaction to adjust all of the statement entries + // These are all disbursements against the charge we're reversing + $rollback = + array('control' => + array('include_ledger_entry' => false, + 'include_statement_entry' => true, + ), + + 'Transaction' => + array('stamp' => $stamp, + 'type' => 'CREDIT_NOTE', + 'crdr' => 'CREDIT', + 'account_id' => $this->Account->accountReceivableAccountID(), + 'amount' => $charge['StatementEntry']['amount'], + 'customer_id' => $charge['StatementEntry']['customer_id'], + 'lease_id' => null, + 'comment' => $comment, + ), + + 'Entry' => array()); + + // Reverse the charge + $rollback['Entry'][] = + array('include_ledger_entry' => true, + 'include_statement_entry' => true, + 'type' => 'REVERSAL', + 'account_id' => $charge['StatementEntry']['account_id'], + 'amount' => $charge['StatementEntry']['amount'], + 'customer_id' => $charge['StatementEntry']['customer_id'], + 'lease_id' => $charge['StatementEntry']['lease_id'], + 'charge_entry_id' => $charge['StatementEntry']['id'], + ); + + $customer_credit = 0; + foreach ($disb_entries['DisbursementEntry'] AS $disbursement) { + $rollback['Entry'][] = + array( + 'include_ledger_entry' => + ($disbursement['type'] !== 'DISBURSEMENT'), + + 'force_positive' => true, + 'type' => $disbursement['type'], + 'amount' => -1 * $disbursement['amount'], + 'account_id' => + ($disbursement['type'] === 'DISBURSEMENT' + ? $this->Account->customerCreditAccountID() + : $disbursement['account_id']), + 'customer_id' => $disbursement['customer_id'], + 'lease_id' => $disbursement['lease_id'], + 'charge_entry_id' => $disbursement['charge_entry_id'], + ); + + if ($disbursement['type'] === 'DISBURSEMENT') + $customer_credit += $disbursement['amount']; + } + + // Create an explicit surplus entry for the customer credit. + // Do it BEFORE assigning credits to outstanding charges to + // ensure that those charges are paid from the customer surplus + // account thus and we don't end up with bizarre disbursements, + // like having Rent paid from Damage (or whatever account the + // reversed charge came from). + $rollback['control']['assign'] = array($customer_credit, 'assign'); + + // Record the transaction, which will un-disburse previously + // disbursed payments, and other similar work. + if (count($rollback['Entry'])) { + $rollback_result = $this->addTransaction($rollback['control'], + $rollback['Transaction'], + $rollback['Entry']); + $this->pr(20, compact('rollback', 'rollback_result')); + $ret = $rollback_result; + if ($rollback_result['error']) + return $this->prReturn(array('error' => true) + $ret); + } + + return $this->prReturn($ret + array('error' => false)); + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Deletes a transaction and associated entries + * - !!WARNING!! This should be used with EXTREME caution, as it + * irreversibly destroys the data. It is not for normal use, + * and can leave the database in an inconsistent state. Expected + * scenario is to remove a bad transaction directly after creation, + * before it gets tied to anything else in the system (such as + * a late charge invoice for a customer that isn't actually late). + */ + + function destroy($id) { + $this->prFunctionLevel(30); + $this->prEnter(compact('id')); +/* $transaction = $this->find */ +/* ('first', */ +/* array('contain' => */ +/* array(// Models */ +/* 'StatementEntry', */ +/* 'LedgerEntry' => array('Tender'), */ +/* ), */ +/* 'conditions' => array(array('Transaction.id' => $id)), */ +/* )); */ +/* pr($transaction); */ + + $this->id = $id; + $customer_id = $this->field('customer_id'); + $result = $this->delete($id); + + if (!empty($customer_id)) { + $this->StatementEntry->assignCredits + (null, null, null, null, $customer_id, null); + //$this->Customer->update($customer_id); + } + + return $this->prReturn($result); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: update + * - Update any cached or calculated fields + */ + function update($id) { + $this->INTERNAL_ERROR("Transaction::update not yet implemented"); + + $result = $this->find + ('first', + array('link' => array('StatementEntry'), + 'fields' => array("SUM(LedgerEntry.amount) AS total"), + 'conditions' => array(array('LedgerEntry.account_id = Transaction.account_id'), + ), + )); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested transaction + */ + function stats($id = null, $query = null, $balance_account_id = null) { + $this->prEnter(compact('id', 'query')); + + $this->queryInit($query); + unset($query['group']); + + if (isset($id)) { + $query['conditions'][] = array('Transaction.id' => $id); + $query['group'] = 'Transaction.id'; + } + else + // CakePHP seems to automagically add in our ID as a part + // of the query conditions, but only on a 'first' query, + // not an 'all'. I suppose this is helpful :-/ + unset($this->id); + + if (empty($query['fields'])) + $query['fields'] = array(); + + // Get the overall total + $squery = $query; + $squery['fields'][] = "SUM(Transaction.amount) AS total"; + $squery['fields'][] = "COUNT(Transaction.id) AS count"; + $stats = $this->find('first', $squery); + if (empty($stats)) + return $this->prReturn(array()); + $stats = $stats[0]; + unset($stats[0]); + + + foreach ($this->hasMany AS $table => $association) { + // Only calculate stats for *Entry types + if (!preg_match("/Entry$/", $table) && + !preg_match("/Entry$/", $association['className'])) + continue; + + $squery = $query; + $squery['link'][$table] = array('fields' => array()); + + if ($table == 'LedgerEntry') { + if (isset($balance_account_id)) { + $squery['link']['LedgerEntry']['Account'] = array('fields' => array()); + $squery['conditions'][] = array("Account.id" => $balance_account_id); + } + + $squery['fields'] = array_merge($squery['fields'], + $this->LedgerEntry->debitCreditFields(true, $balance_account_id != null)); + } + elseif ($table == 'StatementEntry') { + $squery['fields'] = array_merge($squery['fields'], + $this->StatementEntry->chargeDisbursementFields(true)); + } + else { + $squery['fields'][] = "SUM({$table}.amount) AS total"; + $squery['fields'][] = "COUNT({$table}.id) AS entries"; + } + $stats[$table] = $this->find('first', $squery); + // REVISIT : 20090724 + // [0][0] is for when we do an 'all' query. This can + // be removed at some point, but I'm keeping it while + // toggling between 'all' and 'first' (testing). + if (isset($stats[$table][0][0])) + $stats[$table] += $stats[$table][0][0]; + else + $stats[$table] += $stats[$table][0]; + unset($stats[$table][0]); + } + + // Add summary data, which may or may not be useful + // or even meaningful, depending on what the caller + // has queried (it's up to them to make that decision). + $stats['undisbursed'] = $stats['total'] - $stats['StatementEntry']['disbursements']; + + return $this->prReturn($stats); + } +} +?> \ No newline at end of file diff --git a/models/unit.php b/models/unit.php new file mode 100644 index 0000000..602460a --- /dev/null +++ b/models/unit.php @@ -0,0 +1,230 @@ + array('numeric'), + 'unit_size_id' => array('numeric'), + 'name' => array('notempty'), + 'sort_order' => array('numeric'), + 'walk_order' => array('numeric'), + 'deposit' => array('money'), + 'amount' => array('money') + ); + + var $belongsTo = array( + 'UnitSize', + ); + + var $hasOne = array( + 'CurrentLease' => array( + 'className' => 'Lease', + 'conditions' => 'CurrentLease.moveout_date IS NULL', + ), + ); + + var $hasMany = array( + 'Lease', + ); + + //var $default_log_level = array('log' => 30, 'show' => 15); + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * helpers: status enumerations + */ + + function statusEnums() { + static $status_enums; + if (!isset($status_enums)) + $status_enums = $this->getEnumValues('status'); + return $status_enums; + } + + function activeStatusEnums() { + return array_diff_key($this->statusEnums(), array(''=>1, 'DELETED'=>1)); + } + + function statusValue($enum) { + $enums = $this->statusEnums(); + return $enums[$enum]; + } + + function occupiedEnumValue() { + return $this->statusValue('OCCUPIED'); + } + + function statusCheck($id_or_enum, + $min = null, $min_strict = false, + $max = null, $max_strict = false) + { + $this->prEnter(compact('id_or_enum', 'min', 'min_strict', 'max', 'max_strict')); + + if (is_int($id_or_enum)) { + $this->id = $id_or_enum; + $id_or_enum = $this->field('status'); + } + + $enum_val = $this->statusValue($id_or_enum); + if (isset($min) && is_string($min)) + $min = $this->statusValue($min); + if (isset($max) && is_string($max)) + $max = $this->statusValue($max); + + $this->pr(17, compact('enum_val', 'min', 'min_strict', 'max', 'max_strict')); + + if (isset($min) && + ($enum_val < $min || + ($min_strict && $enum_val == $min))) + return $this->prReturn(false); + + if (isset($max) && + ($enum_val > $max || + ($max_strict && $enum_val == $max))) + return $this->prReturn(false); + + return $this->prReturn(true); + } + + function occupied($enum) { + return $this->statusCheck($enum, 'OCCUPIED', false, null, false); + } + + function conditionOccupied() { + return ('Unit.status >= ' . $this->statusValue('OCCUPIED')); + } + + function vacant($enum) { + return $this->statusCheck($enum, 'UNAVAILABLE', true, 'OCCUPIED', true); + } + + function conditionVacant() { + return ('Unit.status BETWEEN ' . + ($this->statusValue('UNAVAILABLE')+1) . + ' AND ' . + ($this->statusValue('OCCUPIED')-1)); + } + + function unavailable($enum) { + return $this->statusCheck($enum, null, false, 'UNAVAILABLE', false); + } + + function conditionUnavailable() { + return ('Unit.status <= ' . $this->statusValue('UNAVAILABLE')); + } + + function available($enum) { return $this->vacant($enum); } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: allowedStatusSet + * - Returns the status set allowed for the given unit + */ + function allowedStatusSet($id) { + $this->prEnter(compact('id')); + $this->id = $id; + $old_status = $this->field('status'); + $old_val = $this->statusValue($old_status); + $this->pr(17, compact('old_status', 'old_val')); + + $enums = $this->activeStatusEnums(); + $this->pr(21, compact('enums')); + + foreach ($enums AS $enum => $val) { + if (($old_val < $this->occupiedEnumValue()) != + ($val < $this->occupiedEnumValue())) { + unset($enums[$enum]); + } + } + + return $this->prReturn($enums); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: updateStatus + * - Update the given unit to the given status + */ + function updateStatus($id, $status, $check = false) { + $this->prEnter(compact('id', 'status', 'check')); + +/* if ($check) { */ +/* $old_status = $this->field('status'); */ +/* $this->pr(17, compact('old_status')); */ +/* if ($this->statusValue($old_status) < $this->occupiedEnumValue() && */ +/* $this->statusValue($status) >= $this->occupiedEnumValue()) */ +/* { */ +/* die("Can't transition a unit from vacant to occupied"); */ +/* return $this->prReturn(false); */ +/* } */ +/* if ($this->statusValue($old_status) >= $this->occupiedEnumValue() && */ +/* $this->statusValue($status) < $this->occupiedEnumValue()) */ +/* { */ +/* die("Can't transition a unit from occupied to vacant"); */ +/* return $this->prReturn(false); */ +/* } */ +/* } */ + + if ($check) { + if (!array_key_exists($status, $this->allowedStatusSet($id))) + return $this->prReturn(false); + } + + $this->id = $id; + $this->saveField('status', $status); + return $this->prReturn(true); + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: update + * - Update any cached or calculated fields + */ + function update($id) { + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: stats + * - Returns summary data from the requested customer. + */ + + function stats($id = null) { + if (!$id) + return null; + + // Get the basic information necessary + $unit = $this->find('first', + array('contain' => array + ('Lease' => array + ('fields' => array('Lease.id')), + + 'CurrentLease' => array + ('fields' => array('CurrentLease.id'))), + + 'conditions' => array + (array('Unit.id' => $id)), + )); + + // Get the stats for the current lease + $stats['CurrentLease'] = $this->Lease->stats($unit['CurrentLease']['id']); + + // Sum the stats for all leases together + foreach ($unit['Lease'] AS $lease) { + $this->statsMerge($stats['Lease'], $this->Lease->stats($lease['id'])); + } + + // Return the collection + return $stats; + } + +} diff --git a/models/unit_size.php b/models/unit_size.php new file mode 100644 index 0000000..ca06b60 --- /dev/null +++ b/models/unit_size.php @@ -0,0 +1,25 @@ + array('numeric'), + 'unit_type_id' => array('numeric'), + 'code' => array('notempty'), + 'name' => array('notempty'), + 'width' => array('numeric'), + 'depth' => array('numeric'), + 'deposit' => array('money'), + 'amount' => array('money') + ); + + var $belongsTo = array( + 'UnitType', + ); + + var $hasMany = array( + 'Unit', + ); + +} +?> \ No newline at end of file diff --git a/models/unit_type.php b/models/unit_type.php new file mode 100644 index 0000000..d2bd90d --- /dev/null +++ b/models/unit_type.php @@ -0,0 +1,16 @@ + array('numeric'), + 'code' => array('notempty'), + 'name' => array('notempty') + ); + + var $hasMany = array( + 'UnitSize', + ); + +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/README b/plugins/debug_kit/README new file mode 100644 index 0000000..4f31f67 --- /dev/null +++ b/plugins/debug_kit/README @@ -0,0 +1,5 @@ +To install copy the debug_kit directory to the plugins folder and include the toolbar component in your app_controller.php: + +$components = array('DebugKit.Toolbar'); + ++ Set debug mode to at least 1. \ No newline at end of file diff --git a/plugins/debug_kit/controllers/components/empty b/plugins/debug_kit/controllers/components/empty new file mode 100644 index 0000000..e69de29 diff --git a/plugins/debug_kit/controllers/components/toolbar.php b/plugins/debug_kit/controllers/components/toolbar.php new file mode 100644 index 0000000..0b72acb --- /dev/null +++ b/plugins/debug_kit/controllers/components/toolbar.php @@ -0,0 +1,471 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +class ToolbarComponent extends Object { +/** + * Controller instance reference + * + * @var object + */ + var $controller; +/** + * Components used by DebugToolbar + * + * @var array + */ + var $components = array('RequestHandler'); +/** + * The default panels the toolbar uses. + * which panels are used can be configured when attaching the component + * + * @var array + */ + var $_defaultPanels = array('session', 'request', 'sqlLog', 'timer', 'log', 'memory', 'variables'); +/** + * Loaded panel objects. + * + * @var array + */ + var $panels = array(); + +/** + * fallback for javascript settings + * + * @var array + **/ + var $_defaultJavascript = array( + 'behavior' => '/debug_kit/js/js_debug_toolbar' + ); +/** + * javascript files component will be using. + * + * @var array + **/ + var $javascript = array(); +/** + * initialize + * + * If debug is off the component will be disabled and not do any further time tracking + * or load the toolbar helper. + * + * @return bool + **/ + function initialize(&$controller, $settings) { + if (Configure::read('debug') == 0) { + $this->enabled = false; + return false; + } + App::import('Vendor', 'DebugKit.DebugKitDebugger'); + + DebugKitDebugger::startTimer('componentInit', __('Component initialization and startup', true)); + if (!isset($settings['panels'])) { + $settings['panels'] = $this->_defaultPanels; + } + + if (isset($settings['javascript'])) { + $settings['javascript'] = $this->_setJavascript($settings['javascript']); + } else { + $settings['javascript'] = $this->_defaultJavascript; + } + $this->_loadPanels($settings['panels']); + unset($settings['panels']); + + $this->_set($settings); + $this->controller =& $controller; + return false; + } + +/** + * Component Startup + * + * @return bool + **/ + function startup(&$controller) { + $currentViewClass = $controller->view; + $this->_makeViewClass($currentViewClass); + $controller->view = 'DebugKit.Debug'; + if (!isset($controller->params['url']['ext']) || (isset($controller->params['url']['ext']) && $controller->params['url']['ext'] == 'html')) { + $format = 'Html'; + } else { + $format = 'FirePhp'; + } + $controller->helpers['DebugKit.Toolbar'] = array('output' => sprintf('DebugKit.%sToolbar', $format)); + $panels = array_keys($this->panels); + foreach ($panels as $panelName) { + $this->panels[$panelName]->startup($controller); + } + DebugKitDebugger::stopTimer('componentInit'); + DebugKitDebugger::startTimer('controllerAction', __('Controller Action', true)); + } +/** + * beforeRender callback + * + * Calls beforeRender on all the panels and set the aggregate to the controller. + * + * @return void + **/ + function beforeRender(&$controller) { + DebugKitDebugger::stopTimer('controllerAction'); + $vars = array(); + $panels = array_keys($this->panels); + + foreach ($panels as $panelName) { + $panel =& $this->panels[$panelName]; + $vars[$panelName]['content'] = $panel->beforeRender($controller); + $elementName = Inflector::underscore($panelName) . '_panel'; + if (isset($panel->elementName)) { + $elementName = $panel->elementName; + } + $vars[$panelName]['elementName'] = $elementName; + $vars[$panelName]['plugin'] = $panel->plugin; + $vars[$panelName]['disableTimer'] = true; + } + + $controller->set(array('debugToolbarPanels' => $vars, 'debugToolbarJavascript' => $this->javascript)); + DebugKitDebugger::startTimer('controllerRender', __('Render Controller Action', true)); + } + +/** + * Load Panels used in the debug toolbar + * + * @return void + * @access protected + **/ + function _loadPanels($panels) { + foreach ($panels as $panel) { + $className = $panel . 'Panel'; + if (!class_exists($className) && !App::import('Vendor', $className)) { + trigger_error(sprintf(__('Could not load DebugToolbar panel %s', true), $panel), E_USER_WARNING); + continue; + } + $panelObj =& new $className(); + if (is_subclass_of($panelObj, 'DebugPanel') || is_subclass_of($panelObj, 'debugpanel')) { + $this->panels[$panel] =& $panelObj; + } + } + } + +/** + * Set the javascript to user scripts. + * + * Set either script key to false to exclude it from the rendered layout. + * + * @param array $scripts Javascript config information + * @return array + * @access protected + **/ + function _setJavascript($scripts) { + $behavior = false; + if (!is_array($scripts)) { + $scripts = (array)$scripts; + } + if (isset($scripts[0])) { + $behavior = $scripts[0]; + } + if (isset($scripts['behavior'])) { + $behavior = $scripts['behavior']; + } + if (!$behavior) { + return array(); + } elseif ($behavior === true) { + $behavior = 'js'; + } + if (strpos($behavior, '/') !== 0) { + $behavior .= '_debug_toolbar'; + } + $pluginFile = APP . 'plugins' . DS . 'debug_kit' . DS . 'vendors' . DS . 'js' . DS . $behavior . '.js'; + if (file_exists($pluginFile)) { + $behavior = '/debug_kit/js/' . $behavior . '.js'; + } + return compact('behavior'); + } +/** + * Makes the DoppleGangerView class if it doesn't already exist. + * This allows DebugView to be compatible with all view classes. + * + * @param string $baseClassName + * @access protected + * @return void + */ + function _makeViewClass($baseClassName) { + if (!class_exists('DoppelGangerView')) { + App::import('View', $baseClassName); + if (strpos('View', $baseClassName) === false) { + $baseClassName .= 'View'; + } + $class = "class DoppelGangerView extends $baseClassName {}"; + eval($class); + } + } +} + +/** + * Debug Panel + * + * Abstract class for debug panels. + * + * @package cake.debug_kit + */ +class DebugPanel extends Object { +/** + * Defines which plugin this panel is from so the element can be located. + * + * @var string + */ + var $plugin = null; +/** + * startup the panel + * + * Pull information from the controller / request + * + * @param object $controller Controller reference. + * @return void + **/ + function startup(&$controller) { } + +/** + * Prepare output vars before Controller Rendering. + * + * @param object $controller Controller reference. + * @return void + **/ + function beforeRender(&$controller) { } +} + +/** + * Variables Panel + * + * Provides debug information on the View variables. + * + * @package cake.debug_kit.panels + **/ +class VariablesPanel extends DebugPanel { + var $plugin = 'debug_kit'; +} + +/** + * Session Panel + * + * Provides debug information on the Session contents. + * + * @package cake.debug_kit.panels + **/ +class SessionPanel extends DebugPanel { + var $plugin = 'debug_kit'; +/** + * beforeRender callback + * + * @param object $controller + * @access public + * @return array + */ + function beforeRender(&$controller) { + return $controller->Session->read(); + } +} + +/** + * Request Panel + * + * Provides debug information on the Current request params. + * + * @package cake.debug_kit.panels + **/ +class RequestPanel extends DebugPanel { + var $plugin = 'debug_kit'; +/** + * beforeRender callback - grabs request params + * + * @return array + **/ + function beforeRender(&$controller) { + $out = array(); + $out['params'] = $controller->params; + if (isset($controller->Cookie)) { + $out['cookie'] = $controller->Cookie->read(); + } + $out['get'] = $_GET; + $out['currentRoute'] = Router::currentRoute(); + return $out; + } +} + +/** + * Timer Panel + * + * Provides debug information on all timers used in a request. + * + * @package cake.debug_kit.panels + **/ +class TimerPanel extends DebugPanel { + var $plugin = 'debug_kit'; +/** + * startup - add in necessary helpers + * + * @return void + **/ + function startup(&$controller) { + if (!in_array('Number', $controller->helpers)) { + $controller->helpers[] = 'Number'; + } + } +} + +/** + * Memory Panel + * + * Provides debug information on the memory consumption. + * + * @package cake.debug_kit.panels + **/ +class MemoryPanel extends DebugPanel { + var $plugin = 'debug_kit'; +/** + * startup - add in necessary helpers + * + * @return void + **/ + function startup(&$controller) { + if (!in_array('Number', $controller->helpers)) { + $controller->helpers[] = 'Number'; + } + } +} + +/** + * sqlLog Panel + * + * Provides debug information on the SQL logs and provides links to an ajax explain interface. + * + * @package cake.debug_kit.panels + **/ +class sqlLogPanel extends DebugPanel { + var $plugin = 'debug_kit'; + + var $dbConfigs = array(); +/** + * get db configs. + * + * @param string $controller + * @access public + * @return void + */ + function startUp(&$controller) { + if (!class_exists('ConnectionManager')) { + $this->dbConfigs = array(); + return false; + } + $this->dbConfigs = ConnectionManager::sourceList(); + return true; + } +/** + * Get Sql Logs for each DB config + * + * @param string $controller + * @access public + * @return void + */ + function beforeRender(&$controller) { + $queryLogs = array(); + if (!class_exists('ConnectionManager')) { + return array(); + } + foreach ($this->dbConfigs as $configName) { + $db =& ConnectionManager::getDataSource($configName); + if ($db->isInterfaceSupported('showLog')) { + ob_start(); + $db->showLog(); + $queryLogs[$configName] = ob_get_clean(); + } + } + return $queryLogs; + } +} + +/** + * Log Panel - Reads log entries made this request. + * + * @package cake.debug_kit.panels + */ +class LogPanel extends DebugPanel { + var $plugin = 'debug_kit'; +/** + * Log files to scan + * + * @var array + */ + var $logFiles = array('error.log', 'debug.log'); +/** + * startup + * + * @return void + **/ + function startup(&$controller) { + if (!class_exists('CakeLog')) { + App::import('Core', 'Log'); + } + } +/** + * beforeRender Callback + * + * @return array + **/ + function beforeRender(&$controller) { + $this->startTime = DebugKitDebugger::requestStartTime(); + $this->currentTime = DebugKitDebugger::requestTime(); + $out = array(); + foreach ($this->logFiles as $log) { + $file = LOGS . $log; + if (!file_exists($file)) { + continue; + } + $out[$log] = $this->_parseFile($file); + } + return $out; + } +/** + * parse a log file and find the relevant entries + * + * @param string $filename Name of file to read + * @access protected + * @return array + */ + function _parseFile($filename) { + $file =& new File($filename); + $contents = $file->read(); + $timePattern = '/(\d{4}-\d{2}\-\d{2}\s\d{1,2}\:\d{1,2}\:\d{1,2})/'; + $chunks = preg_split($timePattern, $contents, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $len = count($chunks); $i < $len; $i += 2) { + if (strtotime($chunks[$i]) < $this->startTime) { + unset($chunks[$i], $chunks[$i + 1]); + } + } + return array_values($chunks); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/debug_kit_app_controller.php b/plugins/debug_kit/debug_kit_app_controller.php new file mode 100644 index 0000000..0a63efb --- /dev/null +++ b/plugins/debug_kit/debug_kit_app_controller.php @@ -0,0 +1,32 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +class DebugKitAppController extends AppController { + +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/debug_kit_app_model.php b/plugins/debug_kit/debug_kit_app_model.php new file mode 100644 index 0000000..ee7af0b --- /dev/null +++ b/plugins/debug_kit/debug_kit_app_model.php @@ -0,0 +1,32 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +class DebugKitAppModel extends AppModel { + +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/models/empty b/plugins/debug_kit/models/empty new file mode 100644 index 0000000..e69de29 diff --git a/plugins/debug_kit/tests/cases/controllers/components/toolbar.test.php b/plugins/debug_kit/tests/cases/controllers/components/toolbar.test.php new file mode 100644 index 0000000..1435303 --- /dev/null +++ b/plugins/debug_kit/tests/cases/controllers/components/toolbar.test.php @@ -0,0 +1,323 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Component', 'DebugKit.Toolbar'); + +class TestToolbarComponent extends ToolbarComponent { + + function loadPanels($panels) { + $this->_loadPanels($panels); + } + +} + +Mock::generate('DebugPanel'); + +/** +* DebugToolbar Test case +*/ +class DebugToolbarTestCase extends CakeTestCase { + + function setUp() { + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + $this->Controller =& ClassRegistry::init('Controller'); + $this->Controller->Component =& ClassRegistry::init('Component'); + $this->Controller->Toolbar =& ClassRegistry::init('TestToolBarComponent', 'Component'); + } + +/** + * test Loading of panel classes + * + * @return void + **/ + function testLoadPanels() { + $this->Controller->Toolbar->loadPanels(array('session', 'request')); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['session'], 'SessionPanel')); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['request'], 'RequestPanel')); + + $this->expectError(); + $this->Controller->Toolbar->loadPanels(array('randomNonExisting', 'request')); + } + +/** + * test loading of vendor panels from test_app folder + * + * @access public + * @return void + */ + function testVendorPanels() { + $_back = Configure::read('vendorPaths'); + Configure::write('vendorPaths', array(APP . 'plugins' . DS . 'debug_kit' . DS . 'tests' . DS . 'test_app' . DS . 'vendors' . DS)); + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('test'), + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->assertTrue(isset($this->Controller->Toolbar->panels['test'])); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['test'], 'TestPanel')); + + Configure::write('vendorPaths', $_back); + } + +/** + * test initialize + * + * @return void + * @access public + **/ + function testInitialize() { + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + + $this->assertFalse(empty($this->Controller->Toolbar->panels)); + + $timers = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($timers['componentInit'])); + } + +/** + * test startup + * + * @return void + **/ + function testStartup() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('MockDebug') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Toolbar->panels['MockDebug']->expectOnce('startup'); + $this->Controller->Toolbar->startup($this->Controller); + + $this->assertEqual(count($this->Controller->Toolbar->panels), 1); + $this->assertTrue(isset($this->Controller->helpers['DebugKit.Toolbar'])); + $this->assertEqual($this->Controller->helpers['DebugKit.Toolbar'], array('output' => 'DebugKit.HtmlToolbar')); + + $timers = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($timers['controllerAction'])); + } + +/** + * Test Before Render callback + * + * @return void + **/ + function testBeforeRender() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('MockDebug', 'session') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Toolbar->panels['MockDebug']->expectOnce('beforeRender'); + $this->Controller->Toolbar->beforeRender($this->Controller); + + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarPanels'])); + $vars = $this->Controller->viewVars['debugToolbarPanels']; + + $expected = array( + 'plugin' => 'debug_kit', + 'elementName' => 'session_panel', + 'content' => $this->Controller->Session->read(), + 'disableTimer' => true, + ); + $this->assertEqual($expected, $vars['session']); + } + +/** + * test alternate javascript library use + * + * @return void + **/ + function testAlternateJavascript() { + $this->Controller->components = array( + 'DebugKit.Toolbar' + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array( + 'behavior' => '/debug_kit/js/js_debug_toolbar', + ); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'javascript' => 'jquery', + ), + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array( + 'behavior' => '/debug_kit/js/jquery_debug_toolbar.js', + ); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'javascript' => false + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array(); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'javascript' => array('my_library'), + ), + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array( + 'behavior' => 'my_library_debug_toolbar' + ); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'javascript' => array('/my/path/to/file') + ), + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array( + 'behavior' => '/my/path/to/file', + ); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'javascript' => '/js/custom_behavior', + ), + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array( + 'behavior' => '/js/custom_behavior', + ); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + } +/** + * Test alternate javascript existing in the plugin. + * + * @return void + **/ + function testExistingAlterateJavascript() { + $filename = APP . 'plugins' . DS . 'debug_kit' . DS . 'vendors' . DS . 'js' . DS . 'test_alternate_debug_toolbar.js'; + $this->skipIf(!is_writable(dirname($filename)), 'Skipping existing javascript test, debug_kit/vendors/js must be writable'); + + @touch($filename); + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'javascript' => 'test_alternate', + ), + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarJavascript'])); + $expected = array( + 'behavior' => '/debug_kit/js/test_alternate_debug_toolbar.js', + ); + $this->assertEqual($this->Controller->viewVars['debugToolbarJavascript'], $expected); + @unlink($filename); + } +/** + * test the Log panel log reading. + * + * @return void + **/ + function testLogPanel() { + usleep(20); + $this->Controller->log('This is a log I made this request'); + $this->Controller->log('This is the second log I made this request'); + $this->Controller->log('This time in the debug log!', LOG_DEBUG); + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('log', 'session') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->viewVars['debugToolbarPanels']['log']; + + $this->assertEqual(count($result['content']), 2); + $this->assertEqual(count($result['content']['error.log']), 4); + $this->assertEqual(count($result['content']['debug.log']), 2); + + $this->assertEqual(trim($result['content']['debug.log'][1]), 'Debug: This time in the debug log!'); + $this->assertEqual(trim($result['content']['error.log'][1]), 'Error: This is a log I made this request'); + } + + +/** + * teardown + * + * @return void + **/ + function tearDown() { + unset($this->Controller); + if (class_exists('DebugKitDebugger')) { + DebugKitDebugger::clearTimers(); + } + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/tests/cases/empty b/plugins/debug_kit/tests/cases/empty new file mode 100644 index 0000000..e69de29 diff --git a/plugins/debug_kit/tests/cases/test_objects.php b/plugins/debug_kit/tests/cases/test_objects.php new file mode 100644 index 0000000..d7a91dc --- /dev/null +++ b/plugins/debug_kit/tests/cases/test_objects.php @@ -0,0 +1,62 @@ + + * Copyright 2005-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. + * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests + * @package cake.tests + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +/** + * TestFireCake class allows for testing of FireCake + * + * @package debug_kit.tests. + */ +class TestFireCake extends FireCake { + var $sentHeaders = array(); + + function _sendHeader($name, $value) { + $_this = FireCake::getInstance(); + $_this->sentHeaders[$name] = $value; + } +/** + * skip client detection as headers are not being sent. + * + * @access public + * @return void + */ + function detectClientExtension() { + return true; + } +/** + * Reset the fireCake + * + * @return void + **/ + function reset() { + $_this = FireCake::getInstance(); + $_this->sentHeaders = array(); + $_this->_messageIndex = 1; + } +} + +?> diff --git a/plugins/debug_kit/tests/cases/vendors/debug_kit_debugger.test.php b/plugins/debug_kit/tests/cases/vendors/debug_kit_debugger.test.php new file mode 100644 index 0000000..79bbe8b --- /dev/null +++ b/plugins/debug_kit/tests/cases/vendors/debug_kit_debugger.test.php @@ -0,0 +1,157 @@ + + * Copyright 2005-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. + * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests + * @package cake.tests + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Debugger'); +App::import('Vendor', 'DebugKit.DebugKitDebugger'); + +require_once APP . 'plugins' . DS . 'debug_kit' . DS . 'tests' . DS . 'cases' . DS . 'test_objects.php'; + +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs + */ +class DebugKitDebuggerTest extends CakeTestCase { +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + Configure::write('log', false); + if (!defined('SIMPLETESTVENDORPATH')) { + if (file_exists(APP . DS . 'vendors' . DS . 'simpletest' . DS . 'reporter.php')) { + define('SIMPLETESTVENDORPATH', 'APP' . DS . 'vendors'); + } else { + define('SIMPLETESTVENDORPATH', 'CORE' . DS . 'vendors'); + } + } + } + +/** + * Start Timer test + * + * @return void + **/ + function testTimers() { + $this->assertTrue(DebugKitDebugger::startTimer('test1', 'this is my first test')); + usleep(5000); + $this->assertTrue(DebugKitDebugger::stopTimer('test1')); + $elapsed = DebugKitDebugger::elapsedTime('test1'); + $this->assertTrue($elapsed > 0.0050); + + $this->assertTrue(DebugKitDebugger::startTimer('test2', 'this is my second test')); + sleep(1); + $this->assertTrue(DebugKitDebugger::stopTimer('test2')); + $elapsed = DebugKitDebugger::elapsedTime('test2'); + $this->assertTrue($elapsed > 1); + + DebugKitDebugger::startTimer('test3'); + $this->assertFalse(DebugKitDebugger::elapsedTime('test3')); + $this->assertFalse(DebugKitDebugger::stopTimer('wrong')); + } + +/** + * testRequestTime + * + * @access public + * @return void + */ + function testRequestTime() { + $result1 = DebugKitDebugger::requestTime(); + usleep(50); + $result2 = DebugKitDebugger::requestTime(); + $this->assertTrue($result1 < $result2); + } + +/** + * test getting all the set timers. + * + * @return void + **/ + function testGetTimers() { + DebugKitDebugger::clearTimers(); + DebugKitDebugger::startTimer('test1', 'this is my first test'); + DebugKitDebugger::stopTimer('test1'); + usleep(50); + DebugKitDebugger::startTimer('test2'); + DebugKitDebugger::stopTimer('test2'); + $timers = DebugKitDebugger::getTimers(); + + $this->assertEqual(count($timers), 2); + $this->assertTrue(is_float($timers['test1']['time'])); + $this->assertTrue(isset($timers['test1']['message'])); + $this->assertTrue(isset($timers['test2']['message'])); + } + +/** + * test memory usage + * + * @return void + **/ + function testMemoryUsage() { + $result = DebugKitDebugger::getMemoryUse(); + $this->assertTrue(is_int($result)); + + $result = DebugKitDebugger::getPeakMemoryUse(); + $this->assertTrue(is_int($result)); + } +/** + * test _output switch to firePHP + * + * @return void + */ + function testOutput() { + $firecake =& FireCake::getInstance('TestFireCake'); + Debugger::invoke(DebugKitDebugger::getInstance('DebugKitDebugger')); + Debugger::output('fb'); + $foo .= ''; + $result = $firecake->sentHeaders; + + $this->assertPattern('/GROUP_START/', $result['X-Wf-1-1-1-1']); + $this->assertPattern('/ERROR/', $result['X-Wf-1-1-1-2']); + $this->assertPattern('/GROUP_END/', $result['X-Wf-1-1-1-5']); + + Debugger::invoke(Debugger::getInstance('Debugger')); + Debugger::output(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('log', true); + } + +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/tests/cases/vendors/fire_cake.test.php b/plugins/debug_kit/tests/cases/vendors/fire_cake.test.php new file mode 100644 index 0000000..513fb96 --- /dev/null +++ b/plugins/debug_kit/tests/cases/vendors/fire_cake.test.php @@ -0,0 +1,336 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package debug_kit + * @subpackage cake.debug_kit.tests + * @since CakePHP v 1.2.0.4487 + * @version + * @modifiedby + * @lastmodified + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Vendor', 'DebugKit.FireCake'); + +require_once APP . 'plugins' . DS . 'debug_kit' . DS . 'tests' . DS . 'cases' . DS . 'test_objects.php'; +/** + * Test Case For FireCake + * + * @package debug_kit.tests + */ +class FireCakeTestCase extends CakeTestCase { +/** + * setup test + * + * Fill FireCake with TestFireCake instance. + * + * @access public + * @return void + */ + function setUp() { + $this->firecake =& FireCake::getInstance('TestFireCake'); + } +/** + * test getInstance cheat. + * + * If this fails the rest of the test is going to fail too. + * + * @return void + **/ + function testGetInstanceOverride() { + $instance =& FireCake::getInstance(); + $instance2 =& FireCake::getInstance(); + $this->assertReference($instance, $instance2); + $this->assertIsA($instance, 'FireCake'); + $this->assertIsA($instance, 'TestFireCake', 'Stored instance is not a copy of TestFireCake, test case is broken.'); + } +/** + * testsetoption + * + * @return void + **/ + function testSetOptions() { + FireCake::setOptions(array('includeLineNumbers' => false)); + $this->assertEqual($this->firecake->options['includeLineNumbers'], false); + } +/** + * test Log() + * + * @access public + * @return void + */ + function testLog() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::log('Testing'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '26|[{"Type":"LOG"},"Testing"]|'); + + FireCake::log('Testing', 'log-info'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '45|[{"Type":"LOG","Label":"log-info"},"Testing"]|'); + } +/** + * test info() + * + * @access public + * @return void + */ + function testInfo() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::info('I have information'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '38|[{"Type":"INFO"},"I have information"]|'); + + FireCake::info('I have information', 'info-label'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '59|[{"Type":"INFO","Label":"info-label"},"I have information"]|'); + } +/** + * test info() + * + * @access public + * @return void + */ + function testWarn() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::warn('A Warning'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '29|[{"Type":"WARN"},"A Warning"]|'); + + FireCake::warn('A Warning', 'Bzzz'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '44|[{"Type":"WARN","Label":"Bzzz"},"A Warning"]|'); + } +/** + * test error() + * + * @access public + * @return void + **/ + function testError() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::error('An error'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '29|[{"Type":"ERROR"},"An error"]|'); + + FireCake::error('An error', 'wonky'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '45|[{"Type":"ERROR","Label":"wonky"},"An error"]|'); + } +/** + * test dump() + * + * @return void + **/ + function testDump() { + FireCake::dump('mydump', array('one' => 1, 'two' => 2)); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-2-1-1'], '28|{"mydump":{"one":1,"two":2}}|'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-2'])); + } +/** + * test table() generation + * + * @return void + **/ + function testTable() { + $table[] = array('Col 1 Heading','Col 2 Heading'); + $table[] = array('Row 1 Col 1','Row 1 Col 2'); + $table[] = array('Row 2 Col 1','Row 2 Col 2'); + $table[] = array('Row 3 Col 1','Row 3 Col 2'); + FireCake::table('myTrace', $table); + $expected = '162|[{"Type":"TABLE","Label":"myTrace"},[["Col 1 Heading","Col 2 Heading"],["Row 1 Col 1","Row 1 Col 2"],["Row 2 Col 1","Row 2 Col 2"],["Row 3 Col 1","Row 3 Col 2"]]]|'; + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], $expected); + } +/** + * testStringEncoding + * + * @return void + **/ + function testStringEncode() { + $result = $this->firecake->stringEncode(array(1,2,3)); + $this->assertEqual($result, array(1,2,3)); + + $this->firecake->setOptions(array('maxArrayDepth' => 3)); + $deep = array(1 => array(2 => array(3))); + $result = $this->firecake->stringEncode($deep); + $this->assertEqual($result, array(1 => array(2 => '** Max Array Depth (3) **'))); + + $obj =& FireCake::getInstance(); + $result = $this->firecake->stringEncode($obj); + $this->assertTrue(is_array($result)); + $this->assertEqual($result['_defaultOptions']['useNativeJsonEncode'], true); + $this->assertEqual($result['_log'], null); + $this->assertEqual($result['_encodedObjects'][0], '** Recursion (TestFireCake) **'); + } +/** + * test trace() + * + * @return void + **/ + function testTrace() { + FireCake::trace('myTrace'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $dump = $this->firecake->sentHeaders['X-Wf-1-1-1-1']; + $this->assertPattern('/"Message":"myTrace"/', $dump); + $this->assertPattern('/"Trace":\[/', $dump); + } +/** + * test enabling and disabling of FireCake output + * + * @return void + **/ + function testEnableDisable() { + FireCake::disable(); + FireCake::trace('myTrace'); + $this->assertTrue(empty($this->firecake->sentHeaders)); + + FireCake::enable(); + FireCake::trace('myTrace'); + $this->assertFalse(empty($this->firecake->sentHeaders)); + } +/** + * test correct line continuation markers on multi line headers. + * + * @access public + * @return void + */ + function testMultiLineOutput() { + FireCake::trace('myTrace'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 3); + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-1']; + $this->assertEqual(substr($header, -2), '|\\'); + + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-2']; + $this->assertEqual(substr($header, -2), '|\\'); + + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-3']; + $this->assertEqual(substr($header, -1), '|'); + } + +/** + * test inclusion of line numbers + * + * @return void + **/ + function testIncludeLineNumbers() { + FireCake::setOptions(array('includeLineNumbers' => true)); + FireCake::info('Testing'); + $result = $this->firecake->sentHeaders['X-Wf-1-1-1-1']; + $this->assertPattern('/"File"\:"APP.*fire_cake.test.php/', $result); + $this->assertPattern('/"Line"\:\d+/', $result); + } +/** + * test Group messages + * + * @return void + **/ + function testGroup() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::group('test'); + FireCake::info('my info'); + FireCake::groupEnd(); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '44|[{"Type":"GROUP_START","Label":"test"},null]|'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-3'], '27|[{"Type":"GROUP_END"},null]|'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 3); + } +/** + * test fb() parameter parsing + * + * @return void + **/ + function testFbParameterParsing() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::fb('Test'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '23|[{"Type":"LOG"},"Test"]|'); + + FireCake::fb('Test', 'warn'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '24|[{"Type":"WARN"},"Test"]|'); + + FireCake::fb('Test', 'Custom label', 'warn'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-3'], '47|[{"Type":"WARN","Label":"Custom label"},"Test"]|'); + + $this->expectError(); + $this->assertFalse(FireCake::fb('Test', 'Custom label', 'warn', 'more parameters')); + + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 3); + } +/** + * Test defaulting to log if incorrect message type is used + * + * @return void + **/ + function testIncorrectMessageType() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::fb('Hello World', 'foobared'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '30|[{"Type":"LOG"},"Hello World"]|'); + } +/** + * testClientExtensionDetection. + * + * @return void + **/ + function testDetectClientExtension() { + $back = env('HTTP_USER_AGENT'); + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4 FirePHP/0.2.1'; + $this->assertTrue(FireCake::detectClientExtension()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4 FirePHP/0.0.4'; + $this->assertFalse(FireCake::detectClientExtension()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4'; + $this->assertFalse(FireCake::detectClientExtension()); + $_SERVER['HTTP_USER_AGENT'] = $back; + } +/** + * test of Non Native JSON encoding. + * + * @return void + **/ + function testNonNativeEncoding() { + FireCake::setOptions(array('useNativeJsonEncode' => false)); + $json = FireCake::jsonEncode(array('one' => 1, 'two' => 2)); + $this->assertEqual($json, '{"one":1,"two":2}'); + + $json = FireCake::jsonEncode(array(1,2,3)); + $this->assertEqual($json, '[1,2,3]'); + + $json = FireCake::jsonEncode(FireCake::getInstance()); + $this->assertPattern('/"options"\:\{"maxObjectDepth"\:\d*,/', $json); + } +/** + * reset the FireCake counters and headers. + * + * @access public + * @return void + */ + function tearDown() { + TestFireCake::reset(); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/tests/cases/views/debug.test.php b/plugins/debug_kit/tests/cases/views/debug.test.php new file mode 100644 index 0000000..916a67d --- /dev/null +++ b/plugins/debug_kit/tests/cases/views/debug.test.php @@ -0,0 +1,144 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Core', 'View'); + +if (!class_exists('DoppelGangerView')) { + class DoppelGangerView extends View {} +} + +App::import('View', 'DebugKit.Debug'); +App::import('Vendor', 'DebugKit.DebugKitDebugger'); +/** + * Debug View Test Case + * + * @package debug_kit.tests + */ +class DebugViewTestCase extends CakeTestCase { +/** + * set Up test case + * + * @return void + **/ + function setUp() { + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + $this->Controller =& ClassRegistry::init('Controller'); + $this->View =& new DebugView($this->Controller, false); + $this->_debug = Configure::read('debug'); + } + +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + $this->_viewPaths = Configure::read('viewPaths'); + Configure::write('viewPaths', array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + )); + } + +/** + * test that element timers are working + * + * @return void + **/ + function testElementTimers() { + $result = $this->View->element('test_element'); + $this->assertPattern('/^this is the test element$/', $result); + + $result = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($result['render_test_element.ctp'])); + } + +/** + * test rendering and ensure that timers are being set. + * + * @access public + * @return void + */ + function testRenderTimers() { + $this->Controller->viewPath = 'posts'; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => null, + 'here' => '/posts/index', + ); + $this->Controller->layout = 'default'; + $View =& new DebugView($this->Controller, false); + $View->render('index'); + + $result = DebugKitDebugger::getTimers(); + $this->assertEqual(count($result), 3); + $this->assertTrue(isset($result['viewRender'])); + $this->assertTrue(isset($result['render_default.ctp'])); + $this->assertTrue(isset($result['render_index.ctp'])); + } + +/** + * Test for correct loading of helpers into custom view + * + * @return void + */ + function testLoadHelpers() { + $loaded = array(); + $result = $this->View->_loadHelpers($loaded, array('Html', 'Javascript', 'Number')); + $this->assertTrue(is_object($result['Html'])); + $this->assertTrue(is_object($result['Javascript'])); + $this->assertTrue(is_object($result['Number'])); + } + +/** + * reset the view paths + * + * @return void + **/ + function endCase() { + Configure::write('viewPaths', $this->_viewPaths); + } + +/** + * tear down function + * + * @return void + **/ + function tearDown() { + unset($this->View, $this->Controller); + DebugKitDebugger::clearTimers(); + Configure::write('debug', $this->_debug); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/tests/cases/views/helpers/fire_php_toobar.test.php b/plugins/debug_kit/tests/cases/views/helpers/fire_php_toobar.test.php new file mode 100644 index 0000000..60206dc --- /dev/null +++ b/plugins/debug_kit/tests/cases/views/helpers/fire_php_toobar.test.php @@ -0,0 +1,137 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage debug_kit.tests.views.helpers + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Helper', 'DebugKit.FirePhpToolbar'); +App::import('Core', array('View', 'Controller')); +require_once APP . 'plugins' . DS . 'debug_kit' . DS . 'tests' . DS . 'cases' . DS . 'test_objects.php'; + +FireCake::getInstance('TestFireCake'); + +class FirePhpToolbarHelperTestCase extends CakeTestCase { +/** + * setUp + * + * @return void + **/ + function setUp() { + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + + $this->Toolbar =& new ToolbarHelper(array('output' => 'DebugKit.FirePhpToolbar')); + $this->Toolbar->FirePhpToolbar =& new FirePhpToolbarHelper(); + + $this->Controller =& ClassRegistry::init('Controller'); + if (isset($this->_debug)) { + Configure::write('debug', $this->_debug); + } + } +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + $this->_viewPaths = Configure::read('viewPaths'); + Configure::write('viewPaths', array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + )); + $this->_debug = Configure::read('debug'); + $this->firecake =& FireCake::getInstance(); + } +/** + * test neat array (dump)creation + * + * @return void + */ + function testMakeNeatArray() { + $this->Toolbar->makeNeatArray(array(1,2,3)); + $result = $this->firecake->sentHeaders; + $this->assertTrue(isset($result['X-Wf-1-1-1-1'])); + $this->assertPattern('/\[1,2,3\]/', $result['X-Wf-1-1-1-1']); + } +/** + * testAfterlayout element rendering + * + * @return void + */ + function testAfterLayout(){ + $this->Controller->viewPath = 'posts'; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index', 'ext' => 'xml'), + 'base' => null, + 'here' => '/posts/index', + ); + $this->Controller->layout = 'default'; + $this->Controller->uses = null; + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $this->assertNoPattern('/debug-toolbar/', $result); + $result = $this->firecake->sentHeaders; + $this->assertTrue(is_array($result)); + + } +/** + * endTest() + * + * @return void + */ + function endTest() { + TestFireCake::reset(); + } +/** + * reset the view paths + * + * @return void + **/ + function endCase() { + Configure::write('viewPaths', $this->_viewPaths); + } + +/** + * tearDown + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Toolbar, $this->Controller); + ClassRegistry::removeObject('view'); + ClassRegistry::flush(); + Router::reload(); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/tests/cases/views/helpers/html_toolbar.test.php b/plugins/debug_kit/tests/cases/views/helpers/html_toolbar.test.php new file mode 100644 index 0000000..d924c9e --- /dev/null +++ b/plugins/debug_kit/tests/cases/views/helpers/html_toolbar.test.php @@ -0,0 +1,358 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage debug_kit.tests.views.helpers + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Helper', array('DebugKit.HtmlToolbar', 'Html', 'Javascript')); +App::import('Core', array('View', 'Controller')); + +class HtmlToolbarHelperTestCase extends CakeTestCase { +/** + * setUp + * + * @return void + **/ + function setUp() { + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + + $this->Toolbar =& new ToolbarHelper(array('output' => 'DebugKit.HtmlToolbar')); + $this->Toolbar->HtmlToolbar =& new HtmlToolbarHelper(); + $this->Toolbar->HtmlToolbar->Html =& new HtmlHelper(); + $this->Toolbar->HtmlToolbar->Javascript =& new JavascriptHelper(); + + $this->Controller =& ClassRegistry::init('Controller'); + if (isset($this->_debug)) { + Configure::write('debug', $this->_debug); + } + } + +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + $this->_viewPaths = Configure::read('viewPaths'); + Configure::write('viewPaths', array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + )); + $this->_debug = Configure::read('debug'); + } + +/** + * test Neat Array formatting + * + * @return void + **/ + function testMakeNeatArray() { + $in = false; + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = null; + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = true; + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array('key' => 'value'); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array('key' => null); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array('key' => 'value', 'foo' => 'bar'); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array( + 'key' => 'value', + 'foo' => array( + 'this' => 'deep', + 'another' => 'value' + ) + ); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + ' array('class' => 'neat-array depth-1')), + 'assertTags($result, $expected); + + $in = array( + 'key' => 'value', + 'foo' => array( + 'this' => 'deep', + 'another' => 'value' + ), + 'lotr' => array( + 'gandalf' => 'wizard', + 'bilbo' => 'hobbit' + ) + ); + $result = $this->Toolbar->makeNeatArray($in, 1); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0 expanded'), + ' array('class' => 'neat-array depth-1')), + ' array('class' => 'neat-array depth-1')), + 'assertTags($result, $expected); + + $result = $this->Toolbar->makeNeatArray($in, 2); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0 expanded'), + ' array('class' => 'neat-array depth-1 expanded')), + ' array('class' => 'neat-array depth-1 expanded')), + 'assertTags($result, $expected); + + $in = array('key' => 'value', 'array' => array()); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + } + +/** + * Test injection of toolbar + * + * @return void + **/ + function testInjectToolbar() { + $this->Controller->viewPath = 'posts'; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => null, + 'here' => '/posts/index', + ); + $this->Controller->helpers = array('Html', 'Javascript', 'DebugKit.Toolbar'); + $this->Controller->layout = 'default'; + $this->Controller->uses = null; + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $result = str_replace(array("\n", "\r"), '', $result); + $this->assertPattern('#
.+
#', $result); + } + +/** + * test injection of javascript + * + * @return void + **/ + function testJavascriptInjection() { + $this->Controller->viewPath = 'posts'; + $this->Controller->uses = null; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => '/', + 'here' => '/posts/index', + ); + $this->Controller->helpers = array('Javascript', 'Html'); + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->layout = 'default'; + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $result = str_replace(array("\n", "\r"), '', $result); + $this->assertPattern('#\s?#', $result); + } + +/** + * test Injection of user defined javascript + * + * @return void + **/ + function testCustomJavascriptInjection() { + $this->Controller->viewPath = 'posts'; + $this->Controller->uses = null; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => '/', + 'here' => '/posts/index', + ); + $this->Controller->helpers = array('Javascript', 'Html'); + $this->Controller->components = array('DebugKit.Toolbar' => array('javascript' => array('my_custom'))); + $this->Controller->layout = 'default'; + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $result = str_replace(array("\n", "\r"), '', $result); + $this->assertPattern('#\s?#', $result); + } +/** + * test message creation + * + * @return void + */ + function testMessage() { + $result = $this->Toolbar->message('test', 'one, two'); + $expected = array( + 'assertTags($result, $expected); + } +/** + * Test Table generation + * + * @return void + */ + function testTable() { + $rows = array( + array(1,2), + array(3,4), + ); + $result = $this->Toolbar->table($rows); + $expected = array( + 'table' => array('class' =>'debug-table'), + array('tr' => array('class' => 'odd')), + ' array('class' => 'even')), + 'assertTags($result, $expected); + } +/** + * reset the view paths + * + * @return void + **/ + function endCase() { + Configure::write('viewPaths', $this->_viewPaths); + } + +/** + * tearDown + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Toolbar, $this->Controller); + ClassRegistry::removeObject('view'); + ClassRegistry::flush(); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/tests/fixtures/empty b/plugins/debug_kit/tests/fixtures/empty new file mode 100644 index 0000000..e69de29 diff --git a/plugins/debug_kit/tests/groups/empty b/plugins/debug_kit/tests/groups/empty new file mode 100644 index 0000000..e69de29 diff --git a/plugins/debug_kit/tests/test_app/vendors/test_panel.php b/plugins/debug_kit/tests/test_app/vendors/test_panel.php new file mode 100644 index 0000000..26a3de9 --- /dev/null +++ b/plugins/debug_kit/tests/test_app/vendors/test_panel.php @@ -0,0 +1,33 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake.debug_kit + * @subpackage cake.debug_kit.tests + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +class TestPanel extends DebugPanel { + function startup(&$controller) { + $controller->testPanel = true; + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/vendors/css/debug_toolbar.css b/plugins/debug_kit/vendors/css/debug_toolbar.css new file mode 100644 index 0000000..cbcb274 --- /dev/null +++ b/plugins/debug_kit/vendors/css/debug_toolbar.css @@ -0,0 +1,165 @@ +/* @override http://localhost/cake_debug_kit/debug_kit/css/debug_toolbar.css */ +#debug-kit-toolbar { + position: fixed; + top: 0px; + right:0px; + width: 100%; + height: 1%; + overflow: visible; + z-index:10000; +} +/* panel tabs */ +#debug-kit-toolbar #panel-tabs { + float: right; + list-style: none; + margin: 0; +} +#debug-kit-toolbar .panel-tab { + clear: none; + float: left; + margin: 0; + padding: 0; + list-style: none; +} + +#debug-kit-toolbar .panel-tab a { + float: left; + clear: none; + background: #efefef; + color: #222; + padding: 6px; + border-right: 1px solid #ccc; + font-size: 12px; + line-height: 16px; + margin: 0; + display: block; +} +#debug-kit-toolbar .panel-tab .active, +#debug-kit-toolbar .panel-tab a:hover { + background: #fff; +} +#debug-kit-toolbar .panel-tab.icon a { + padding: 4px; +} + +/* Hovering over link shows tab, useful for no js */ +#debug-kit-toolbar .panel-tab a:hover + .panel-content, +#debug-kit-toolbar .panel-tab a + .panel-content:hover { + display: block; +} + +/* panel content */ +#debug-kit-toolbar .panel-content { + position: absolute; + text-align: left; + width: auto; + top:28px; + right:0px; + background: #fff; + color: #000; + width:96%; + padding:20px 2%; + max-height: 550px; + overflow:auto; + border-bottom: 3px solid #333; +} + +/* Hide panel content by default */ +.panel-content { + display: none; +} + +.panel-content p { + margin: 1em 0; +} +.panel-content h2 { + padding: 0; + margin-top:0; +} +.panel-content h3 { + padding: 0; + margin-top: 1em; +} +.panel-content .info { + padding: 4px; + border-top: 1px dashed #6c6cff; + border-bottom: 1px dashed #6c6cff; +} + +/* panel tables */ +table.debug-table { + width: auto; + border: 0; +} +table.debug-table td, +table.debug-table th { + text-align: left; + border: 0; + padding: 3px; +} +table.debug-table th { + border-bottom: 1px solid #222; + background: 0;; +} +table.debug-table tr.even td { + background:#efefef; +} + +/** code tables **/ +#debug-kit-toolbar .code-table td { + white-space: pre; + font-family: monaco, corsiva, "courier new", courier, monospaced; +} +#debug-kit-toolbar .code-table td:first-child { + width: 15%; +} +#debug-kit-toolbar .code-table td:last-child { + width: 80%; +} + + +.panel-content.request { + display: block; +} + + +/** Neat Array styles **/ +.neat-array { + padding: 1px 2px 1px 20px; + background: #CE9E23; + list-style: none; + margin: 0; +} +.neat-array .neat-array { + padding: 0 0 0 20px; +} +.neat-array li { + background: #FEF6E5; + border-top: 1px solid #CE9E23; + border-bottom: 1px solid #CE9E23; + margin: 0; + line-height: 1.5em; +} +.neat-array li:hover { + background: #fff; +} +.neat-array li strong { + padding: 0 8px; +} + +/* expandable sections */ +.neat-array li.expandable { + cursor: pointer; +} +.neat-array li.expandable.expanded > strong:before { + content: 'v '; +} +.neat-array li.expandable.collapsed > strong:before, +.neat-array li.expandable.expanded .expandable.collapsed > strong:before { + content: '> '; +} + +.neat-array li { + cursor: default; +} + diff --git a/plugins/debug_kit/vendors/debug_kit_debugger.php b/plugins/debug_kit/vendors/debug_kit_debugger.php new file mode 100644 index 0000000..a1d111f --- /dev/null +++ b/plugins/debug_kit/vendors/debug_kit_debugger.php @@ -0,0 +1,226 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Core', 'Debugger'); +App::import('Vendor', 'DebugKit.FireCake'); +/** + * Debug Kit Temporary Debugger Class + * + * Provides the future features that are planned. Yet not implemented in the 1.2 code base + * + * This file will not be needed in future version of CakePHP. + * @todo merge these changes with core Debugger + */ +class DebugKitDebugger extends Debugger { + +/** + * Start an benchmarking timer. + * + * @param string $name The name of the timer to start. + * @param string $message A message for your timer + * @return bool true + * @static + **/ + function startTimer($name = 'default', $message = '') { + $now = getMicrotime(); + $_this = DebugKitDebugger::getInstance(); + $_this->__benchmarks[$name] = array( + 'start' => $now, + 'message' => $message, + ); + return true; + } + +/** + * Stop a benchmarking timer. + * + * $name should be the same as the $name used in startTimer(). + * + * @param string $name The name of the timer to end. + * @access public + * @return boolean true if timer was ended, false if timer was not started. + * @static + */ + function stopTimer($name = 'default') { + $now = getMicrotime(); + $_this = DebugKitDebugger::getInstance(); + if (!isset($_this->__benchmarks[$name])) { + return false; + } + $_this->__benchmarks[$name]['end'] = $now; + return true; + } + +/** + * Get all timers that have been started and stopped. + * Calculates elapsed time for each timer. + * + * @return array + **/ + function getTimers() { + $_this =& DebugKitDebugger::getInstance(); + $times = array(); + foreach ($_this->__benchmarks as $name => $timer) { + $times[$name]['time'] = DebugKitDebugger::elapsedTime($name); + $times[$name]['message'] = $timer['message']; + } + return $times; + } + +/** + * Clear all existing timers + * + * @return bool true + **/ + function clearTimers() { + $_this =& DebugKitDebugger::getInstance(); + $_this->__benchmarks = array(); + return true; + } + +/** + * Get the difference in time between the timer start and timer end. + * + * @param $name string the name of the timer you want elapsed time for. + * @param $precision int the number of decimal places to return, defaults to 5. + * @return float number of seconds elapsed for timer name, 0 on missing key + * @static + **/ + function elapsedTime($name = 'default', $precision = 5) { + $_this =& DebugKitDebugger::getInstance(); + if (!isset($_this->__benchmarks[$name]['start']) || !isset($_this->__benchmarks[$name]['end'])) { + return 0; + } + return round($_this->__benchmarks[$name]['end'] - $_this->__benchmarks[$name]['start'], $precision); + } + +/** + * Get the total execution time until this point + * + * @access public + * @return float elapsed time in seconds since script start. + * @static + */ + function requestTime() { + $start = DebugKitDebugger::requestStartTime(); + $now = getMicroTime(); + return ($now - $start); + } +/** + * get the time the current request started. + * + * @access public + * @return float time of request start + * @static + */ + function requestStartTime() { + if (defined('TIME_START')) { + $startTime = TIME_START; + } else if (isset($_GLOBALS['TIME_START'])) { + $startTime = $_GLOBALS['TIME_START']; + } else { + $startTime = env('REQUEST_TIME'); + } + return $startTime; + } + +/** + * get current memory usage + * + * @return integer number of bytes ram currently in use. 0 if memory_get_usage() is not available. + * @static + **/ + function getMemoryUse() { + if (!function_exists('memory_get_usage')) { + return 0; + } + return memory_get_usage(); + } + +/** + * Get peak memory use + * + * @return integer peak memory use (in bytes). Returns 0 if memory_get_peak_usage() is not available + * @static + **/ + function getPeakMemoryUse() { + if (!function_exists('memory_get_peak_usage')) { + return 0; + } + return memory_get_peak_usage(); + } + +/** + * Handles object conversion to debug string. + * + * @param string $var Object to convert + * @access protected + */ + function _output($level, $error, $code, $helpCode, $description, $file, $line, $kontext) { + $files = $this->trace(array('start' => 2, 'format' => 'points')); + $listing = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1); + $trace = $this->trace(array('start' => 2, 'depth' => '20')); + $context = array(); + + foreach ((array)$kontext as $var => $value) { + $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1); + } + if ($this->_outputFormat == 'fb') { + $this->_fireError($error, $code, $description, $file, $line, $trace, $context); + } else { + echo parent::_output($level, $error, $code, $helpCode, $description, $file, $line, $kontext); + } + } +/** + * Create a FirePHP error message + * + * @param string $error Name of error + * @param string $code Code of error + * @param string $description Description of error + * @param string $file File error occured in + * @param string $line Line error occured on + * @param string $trace Stack trace at time of error + * @param string $context context of error + * @return void + * @access protected + */ + function _fireError($error, $code, $description, $file, $line, $trace, $context) { + $name = $error . ' - ' . $description; + $message = "$error $code $description on line: $line in file: $file"; + FireCake::group($name); + FireCake::error($message, $name); + FireCake::log($context, 'Context'); + FireCake::log($trace, 'Trace'); + FireCake::groupEnd(); + } +} + + +Debugger::invoke(DebugKitDebugger::getInstance()); +Debugger::getInstance('DebugKitDebugger'); +?> \ No newline at end of file diff --git a/plugins/debug_kit/vendors/fire_cake.php b/plugins/debug_kit/vendors/fire_cake.php new file mode 100644 index 0000000..6984439 --- /dev/null +++ b/plugins/debug_kit/vendors/fire_cake.php @@ -0,0 +1,539 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package debug_kit. + * @subpackage debug_kit.vendors + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Core', 'Debugger'); + +class FireCake extends Object { +/** + * Options for FireCake. + * + * @see _defaultOptions and setOptions(); + * @var string + */ + var $options = array(); +/** + * Default Options used in CakeFirePhp + * + * @var string + * @access protected + */ + var $_defaultOptions = array( + 'maxObjectDepth' => 10, + 'maxArrayDepth' => 20, + 'useNativeJsonEncode' => true, + 'includeLineNumbers' => true, + ); +/** + * Message Levels for messages sent via FirePHP + * + * @var array + */ + var $_levels = array( + 'log' => 'LOG', + 'info' => 'INFO', + 'warn' => 'WARN', + 'error' => 'ERROR', + 'dump' => 'DUMP', + 'trace' => 'TRACE', + 'exception' => 'EXCEPTION', + 'table' => 'TABLE', + 'groupStart' => 'GROUP_START', + 'groupEnd' => 'GROUP_END', + ); + + var $_version = '0.2.1'; +/** + * internal messageIndex counter + * + * @var int + * @access protected + */ + var $_messageIndex = 1; +/** + * stack of objects encoded by stringEncode() + * + * @var array + **/ + var $_encodedObjects = array(); +/** + * methodIndex to include in tracebacks when using includeLineNumbers + * + * @var array + **/ + var $_methodIndex = array('info', 'log', 'warn', 'error', 'table', 'trace'); +/** + * FireCake output status + * + * @var bool + **/ + var $_enabled = true; +/** + * get Instance of the singleton + * + * @param string $class Class instance to store in the singleton. Used with subclasses and Tests. + * @access public + * @static + * @return void + */ + function &getInstance($class = null) { + static $instance = array(); + if (!empty($class)) { + if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) { + $instance[0] = new $class(); + $instance[0]->setOptions(); + } + } + if (!isset($instance[0]) || !$instance[0]) { + $instance[0] = new FireCake(); + $instance[0]->setOptions(); + } + return $instance[0]; + } +/** + * setOptions + * + * @param array $options Array of options to set. + * @access public + * @static + * @return void + */ + function setOptions($options = array()) { + $_this = FireCake::getInstance(); + if (empty($_this->options)) { + $_this->options = array_merge($_this->_defaultOptions, $options); + } else { + $_this->options = array_merge($_this->options, $options); + } + } +/** + * Return boolean based on presence of FirePHP extension + * + * @access public + * @return boolean + **/ + function detectClientExtension() { + $ua = FireCake::getUserAgent(); + if (!preg_match('/\sFirePHP\/([\.|\d]*)\s?/si', $ua, $match) || !version_compare($match[1], '0.0.6', '>=')) { + return false; + } + return true; + } +/** + * Get the Current UserAgent + * + * @access public + * @static + * @return string UserAgent string of active client connection + **/ + function getUserAgent() { + return env('HTTP_USER_AGENT'); + } +/** + * Disable FireCake output + * All subsequent output calls will not be run. + * + * @return void + **/ + function disable() { + $_this = FireCake::getInstance(); + $_this->_enabled = false; + } +/** + * Enable FireCake output + * + * @return void + **/ + function enable() { + $_this = FireCake::getInstance(); + $_this->_enabled = true; + } +/** + * Convenience wrapper for LOG messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function log($message, $label = null) { + FireCake::fb($message, $label, 'log'); + } +/** + * Convenience wrapper for WARN messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function warn($message, $label = null) { + FireCake::fb($message, $label, 'warn'); + } +/** + * Convenience wrapper for INFO messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function info($message, $label = null) { + FireCake::fb($message, $label, 'info'); + } +/** + * Convenience wrapper for ERROR messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function error($message, $label = null) { + FireCake::fb($message, $label, 'error'); + } +/** + * Convenience wrapper for TABLE messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function table($label, $message) { + FireCake::fb($message, $label, 'table'); + } +/** + * Convenience wrapper for DUMP messages + * + * @param string $message Message to log + * @param string $label Unique label for message + * @access public + * @static + * @return void + */ + function dump($label, $message) { + FireCake::fb($message, $label, 'dump'); + } +/** + * Convenience wrapper for TRACE messages + * + * @param string $label Label for message (optional) + * @access public + * @return void + */ + function trace($label) { + FireCake::fb($label, 'trace'); + } +/** + * Convenience wrapper for GROUP messages + * Messages following the group call will be nested in a group block + * + * @param string $label Label for group (optional) + * @access public + * @return void + */ + function group($label) { + FireCake::fb(null, $label, 'groupStart'); + } +/** + * Convenience wrapper for GROUPEND messages + * Closes a group block + * + * @param string $label Label for group (optional) + * @access public + * @return void + */ + function groupEnd() { + FireCake::fb(null, null, 'groupEnd'); + } +/** + * fb - Send messages with FireCake to FirePHP + * + * Much like FirePHP's fb() this method can be called with various parameter counts + * fb($message) - Just send a message defaults to LOG type + * fb($message, $type) - Send a message with a specific type + * fb($message, $label, $type) - Send a message with a custom label and type. + * + * @param mixed $message Message to output. For other parameters see usage above. + * @static + * @return void + **/ + function fb($message) { + $_this = FireCake::getInstance(); + + if (headers_sent($filename, $linenum)) { + trigger_error(sprintf(__('Headers already sent in %s on line %s. Cannot send log data to FirePHP.', true), $filename, $linenum), E_USER_WARNING); + return false; + } + if (!$_this->_enabled || !$_this->detectClientExtension()) { + return false; + } + + $args = func_get_args(); + $type = $label = null; + switch (count($args)) { + case 1: + $type = $_this->_levels['log']; + break; + case 2: + $type = $args[1]; + break; + case 3: + $type = $args[2]; + $label = $args[1]; + break; + default: + trigger_error(__('Incorrect parameter count for FireCake::fb()', true), E_USER_WARNING); + return false; + } + if (isset($_this->_levels[$type])) { + $type = $_this->_levels[$type]; + } else { + $type = $_this->_levels['log']; + } + + $meta = array(); + $skipFinalObjectEncode = false; + if ($type == $_this->_levels['trace']) { + $trace = debug_backtrace(); + if (!$trace) { + return false; + } + $message = $_this->_parseTrace($trace, $args[0]); + $skipFinalObjectEncode = true; + } + + if ($_this->options['includeLineNumbers']) { + if (!isset($meta['file']) || !isset($meta['line'])) { + $trace = debug_backtrace(); + for ($i = 0, $len = count($trace); $i < $len ; $i++) { + $keySet = (isset($trace[$i]['class']) && isset($trace[$i]['function'])); + $selfCall = ($keySet && $trace[$i]['class'] == 'FireCake' && in_array($trace[$i]['function'], $_this->_methodIndex)); + if ($selfCall) { + $meta['File'] = isset($trace[$i]['file']) ? Debugger::trimPath($trace[$i]['file']) : ''; + $meta['Line'] = isset($trace[$i]['line']) ? $trace[$i]['line'] : ''; + break; + } + } + } + } + + $structureIndex = 1; + if ($type == $_this->_levels['dump']) { + $structureIndex = 2; + $_this->_sendHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1'); + } else { + $_this->_sendHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); + } + + $_this->_sendHeader('X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); + $_this->_sendHeader('X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'. $_this->_version); + + if ($type == $_this->_levels['dump']) { + $dump = $_this->jsonEncode($message); + $msg = '{"' . $label .'":' . $dump .'}'; + } else { + $meta['Type'] = $type; + if ($label !== null) { + $meta['Label'] = $label; + } + $msg = '[' . $_this->jsonEncode($meta) . ',' . $_this->jsonEncode($message, $skipFinalObjectEncode).']'; + } + + $lines = explode("\n", chunk_split($msg, 5000, "\n")); + foreach ($lines as $i => $line) { + if (empty($line)) { + continue; + } + $header = 'X-Wf-1-' . $structureIndex . '-1-' . $_this->_messageIndex; + if (count($lines) > 2) { + $first = ($i == 0) ? strlen($msg) : ''; + $end = ($i < count($lines) - 2) ? '\\' : ''; + $message = $first . '|' . $line . '|' . $end; + $_this->_sendHeader($header, $message); + } else { + $_this->_sendHeader($header, strlen($line) . '|' . $line . '|'); + } + $_this->_messageIndex++; + if ($_this->_messageIndex > 99999) { + trigger_error(__('Maximum number (99,999) of messages reached!', true), E_USER_WARNING); + } + } + $_this->_sendHeader('X-Wf-1-Index', $_this->_messageIndex - 1); + return true; + } +/** + * Parse a debug backtrace + * + * @param array $trace Debug backtrace output + * @access protected + * @return array + **/ + function _parseTrace($trace, $messageName) { + $message = array(); + for ($i = 0, $len = count($trace); $i < $len ; $i++) { + $keySet = (isset($trace[$i]['class']) && isset($trace[$i]['function'])); + $selfCall = ($keySet && $trace[$i]['class'] == 'FireCake'); + if (!$selfCall) { + $message = array( + 'Class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : '', + 'Type' => isset($trace[$i]['type']) ? $trace[$i]['type'] : '', + 'Function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : '', + 'Message' => $messageName, + 'File' => isset($trace[$i]['file']) ? Debugger::trimPath($trace[$i]['file']) : '', + 'Line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : '', + 'Args' => isset($trace[$i]['args']) ? $this->stringEncode($trace[$i]['args']) : '', + 'Trace' => $this->_escapeTrace(array_splice($trace, $i + 1)) + ); + break; + } + } + return $message; + } +/** + * Fix a trace for use in output + * + * @param mixed $trace Trace to fix + * @access protected + * @static + * @return string + **/ + function _escapeTrace($trace) { + for ($i = 0, $len = count($trace); $i < $len; $i++) { + if (isset($trace[$i]['file'])) { + $trace[$i]['file'] = Debugger::trimPath($trace[$i]['file']); + } + if (isset($trace[$i]['args'])) { + $trace[$i]['args'] = $this->stringEncode($trace[$i]['args']); + } + } + return $trace; + } +/** + * Encode non string objects to string. + * Filter out recursion, so no errors are raised by json_encode or $javascript->object() + * + * @param mixed $object Object or variable to encode to string. + * @param int $objectDepth Current Depth in object chains. + * @param int $arrayDepth Current Depth in array chains. + * @static + * @return void + **/ + function stringEncode($object, $objectDepth = 1, $arrayDepth = 1) { + $_this = FireCake::getInstance(); + $return = array(); + if (is_resource($object)) { + return '** ' . (string)$object . '**'; + } + if (is_object($object)) { + if ($objectDepth == $_this->options['maxObjectDepth']) { + return '** Max Object Depth (' . $_this->options['maxObjectDepth'] . ') **'; + } + foreach ($_this->_encodedObjects as $encoded) { + if ($encoded === $object) { + return '** Recursion (' . get_class($object) . ') **'; + } + } + $_this->_encodedObjects[] = $object; + + $return['__className'] = $class = get_class($object); + $properties = (array) $object; + foreach ($properties as $name => $property) { + $return[$name] = FireCake::stringEncode($property, 1, $objectDepth + 1); + } + array_pop($_this->_encodedObjects); + } + if (is_array($object)) { + if ($arrayDepth == $_this->options['maxArrayDepth']) { + return '** Max Array Depth ('. $_this->options['maxArrayDepth'] . ') **'; + } + foreach ($object as $key => $value) { + $return[$key] = FireCake::stringEncode($value, 1, $arrayDepth + 1); + } + } + if (is_string($object) || is_numeric($object) || is_bool($object) || is_null($object)) { + return $object; + } + return $return; + } +/** + * Encode an object into JSON + * + * @param mixed $object Object or array to json encode + * @param boolean $doIt + * @access public + * @static + * @return string + **/ + function jsonEncode($object, $skipEncode = false) { + $_this = FireCake::getInstance(); + if (!$skipEncode) { + $object = FireCake::stringEncode($object); + } + + if (function_exists('json_encode') && $_this->options['useNativeJsonEncode']) { + return json_encode($object); + } else { + return FireCake::_jsonEncode($object); + } + } +/** + * jsonEncode Helper method for PHP4 compatibility + * + * @param mixed $object Something to encode + * @access protected + * @static + * @return string + **/ + function _jsonEncode($object) { + if (!class_exists('JavascriptHelper')) { + App::import('Helper', 'Javascript'); + } + $javascript = new JavascriptHelper(); + $javascript->useNative = false; + return $javascript->object($object); + } +/** + * Send Headers - write headers. + * + * @access protected + * @return void + **/ + function _sendHeader($name, $value) { + header($name . ': ' . $value); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/vendors/img/cake.icon.png b/plugins/debug_kit/vendors/img/cake.icon.png new file mode 100644 index 0000000..f56f321 Binary files /dev/null and b/plugins/debug_kit/vendors/img/cake.icon.png differ diff --git a/plugins/debug_kit/vendors/js/jquery_debug_toolbar.js b/plugins/debug_kit/vendors/js/jquery_debug_toolbar.js new file mode 100644 index 0000000..43b816f --- /dev/null +++ b/plugins/debug_kit/vendors/js/jquery_debug_toolbar.js @@ -0,0 +1,84 @@ +/* SVN FILE: $Id$ */ +/** + * Debug Toolbar Javascript. jQuery 1.2.x compatible. + * + * Long description here. + * + * PHP versions 4 and 5 + * + * CakePHP : Rapid Development Framework + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +(function($) { +$(document).ready(function(){ + DebugKit.Toolbar(); + DebugKit.NeatArray(); +}); + +var DebugKit = {}; +/** + * Create all behaviors for neat array elements + * + */ +DebugKit.NeatArray = function() { + $('.neat-array').find('li:has(ul)').toggle( + function() { + $(this).toggleClass('expanded').removeClass('collapsed').find('ul:first').show(); + }, + function() { + $(this).toggleClass('expanded').addClass('collapsed').find('ul:first').hide(); + } + ).addClass('expandable').addClass('collapsed').find('ul').hide(); +} +/** + * Add behavior for toolbar buttons + * + */ +DebugKit.Toolbar = function() { + var tabCollection = $('#debug-kit-toolbar li > div'); + + $('#debug-kit-toolbar .panel-tab > a').click( + function(e){ + e.preventDefault(); + var targetPanel = $(this.hash + '-tab'); + if (targetPanel.hasClass('active')) { + tabCollection.hide().removeClass('active'); + } else { + tabCollection + .hide().removeClass('active') + .filter(this.hash + '-tab').show().addClass('active'); + } + $('#debug-kit-toolbar .panel-tab > a').removeClass('active'); + $(this).addClass('active'); + } + ); + + //enable hiding of toolbar. + var panelButtons = $('#debug-kit-toolbar .panel-tab:not(.panel-tab.icon)'); + $('#debug-kit-toolbar #hide-toolbar').toggle( + function() { + panelButtons.hide(); + }, + function() { + panelButtons.show(); + } + ); +} +})(jQuery); \ No newline at end of file diff --git a/plugins/debug_kit/vendors/js/js_debug_toolbar.js b/plugins/debug_kit/vendors/js/js_debug_toolbar.js new file mode 100644 index 0000000..9991d47 --- /dev/null +++ b/plugins/debug_kit/vendors/js/js_debug_toolbar.js @@ -0,0 +1,212 @@ +/* SVN FILE: $Id$ */ +/** + * Debug Toolbar Javascript. + * + * Long description here. + * + * PHP versions 4 and 5 + * + * CakePHP : Rapid Development Framework + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +var DebugKit = function(id) { + var undefined, + elements = {}, + panels = {}, + hidden = false; + + this.initialize = function(id) { + elements.toolbar = document.getElementById(id || 'debug-kit-toolbar'); + + if (elements.toolbar === undefined) { + throw new Exception('Toolbar not found, make sure you loaded it.'); + } + + for (var i in elements.toolbar.childNodes) { + var element = elements.toolbar.childNodes[i]; + if (element.nodeName && element.id === 'panel-tabs') { + elements.panel = element; + break; + } + } + + for (var i in elements.panel.childNodes) { + var element = elements.panel.childNodes[i]; + if (element.className && element.className.match(/panel-tab/)) { + this.addPanel(element); + } + } + + var lists = document.getElementsByTagName('ul'), index = 0; + while (lists[index] !== undefined) { + var element = lists[index]; + if (element.className && element.className.match(/neat-array/)) { + neatArray(element); + } + ++index; + } + } +/** + * Add a panel to the toolbar + */ + this.addPanel = function(tab, callback) { + if (!tab.nodeName || tab.nodeName.toUpperCase() !== 'LI') { + throw new Exception('Toolbar not found, make sure you loaded it.'); + } + var panel = { + id : false, + element : tab, + callback : callback, + button : undefined, + content : undefined, + active : false + }; + for (var i in tab.childNodes) { + var element = tab.childNodes[i], + tag = element.nodeName? element.nodeName.toUpperCase(): false; + if (tag === 'A') { + panel.id = element.hash.replace(/^#/, ''); + panel.button = element; + } else if (tag === 'DIV') { + panel.content = element; + } + } + if (!panel.id) { + throw new Exception('invalid element'); + } + + if (panel.button.id && panel.button.id === 'hide-toolbar') { + panel.callback = this.toggleToolbar; + } + + if (panel.callback !== undefined) { + panel.button.onclick = function() { return panel.callback(); }; + } else { + panel.button.onclick = function() { return window.DebugKit.togglePanel(panel.id); }; + } + + panels[panel.id] = panel; + return panel.id; + }; +/** + * Hide/show the toolbar (minimize cake) + */ + this.toggleToolbar = function() { + for (var i in panels) { + var panel = panels[i], + display = hidden? 'block': 'none'; + if (panel.content !== undefined) { + panel.element.style.display = display; + } + } + hidden = !hidden; + return true; + }; +/** + * Toggle a panel + */ + this.togglePanel = function(id) { + if (panels[id] && panels[id].active) { + this.deactivatePanel(true); + } else { + this.deactivatePanel(true); + this.activatePanel(id); + } + } +/** + * Make a panel active. + */ + this.activatePanel = function(id, unique) { + if (panels[id] !== undefined && !panels[id].active) { + var panel = panels[id]; + this.deactivatePanel(true); + if (panel.content !== undefined) { + panel.content.style.display = 'block'; + } + panel.button.className = panel.button.className.replace(/^(.*)$/, '$1 active'); + panel.active = true; + return true; + } + return false; + }; +/** + * Deactivate a panel. use true to hide all panels. + */ + this.deactivatePanel = function(id) { + if (id === true) { + for (var i in panels) { + this.deactivatePanel(i); + } + return true; + } + if (panels[id] !== undefined && panels[id].active) { + var panel = panels[id]; + if (panel.content !== undefined) { + panel.content.style.display = 'none'; + } + panel.button.className = panel.button.className.replace(/ ?(active) ?/, ''); + panel.active = false; + return true; + } + return false; + }; +/** + * Add neat array functionality. + */ + function neatArray(list) { + if (!list.className.match(/depth-0/)) { + var item = list.parentNode; + list.style.display = 'none'; + item.className = (item.className || '').replace(/^(.*)$/, '$1 expandable collapsed'); + item.onclick = function(event) { + //var element = (event === undefined)? this: event.target; + var element = this, + event = event || window.event, + act = Boolean(item === element), + hide = Boolean(list.style.display === 'block'); + if (act && hide) { + list.style.display = 'none'; + item.className = item.className.replace(/expanded|$/, 'collapsed'); + } else if (act) { + list.style.display = 'block'; + item.className = item.className.replace('collapsed', 'expanded'); + } + + if (event.cancelBubble !== undefined) { + event.cancelBubble = true; + } + return false; + } + } + } + + this.initialize(id); +} + +DebugKit.install = function() { + var initializer = window.onload || function() {}; + window.onload = function() { + initializer(); + // makes DebugKit a singletone instance + window.DebugKit = new DebugKit(); + } +} + +DebugKit.install(); \ No newline at end of file diff --git a/plugins/debug_kit/vendors/js/mootools_debug_toolbar.js b/plugins/debug_kit/vendors/js/mootools_debug_toolbar.js new file mode 100644 index 0000000..2f858c6 --- /dev/null +++ b/plugins/debug_kit/vendors/js/mootools_debug_toolbar.js @@ -0,0 +1,95 @@ +/* SVN FILE: $Id$ */ +/** + * Debug Toolbar Javascript. Mootools 1.2 compatible. + * + * Requires Class, Event, Element, and Selectors + * + * PHP versions 4 and 5 + * + * CakePHP : Rapid Development Framework + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +window.addEvent('domready', function() { + new DebugKit(); +}); +var DebugKit = new Class({ + initialize : function() { + this.neatArray(); + this.toolbar(); + }, +/** + * Create all behaviors for neat array elements + */ + neatArray : function() { + $$('#debug-kit-toolbar .neat-array li').each(function(listItem) { + var subUl = listItem.getElement('ul'); + if (subUl) { + listItem.addClass('expandable').addClass('collapsed'); + subUl.setStyle('display', 'none').set('state', 'closed'); + listItem.addEvent('click', function(event) { + event.stop(); + this.toggleClass('expanded').toggleClass('collapsed'); + if (subUl.get('state') == 'closed') { + subUl.setStyle('display', 'block').set('state', 'open'); + } else { + subUl.setStyle('display', 'none').set('state', 'closed'); + } + }) + } + }); + }, + + /** + * Add behavior for toolbar buttons + */ + toolbar : function() { + var tabCollection = $$('#debug-kit-toolbar li > div'); + + $$('#debug-kit-toolbar .panel-tab > a').addEvent('click', function(event) { + event.stop(); + var buttonId = this.hash.substring(1, this.hash.length) + '-tab'; + var targetPanel = $(buttonId); + if (!targetPanel) return; + $$('#debug-kit-toolbar .panel-tab > a').removeClass('active'); + if (targetPanel.hasClass('active')) { + tabCollection.removeClass('active').setStyle('display', 'none'); + } else { + tabCollection.setStyle('display', 'none').removeClass('active'); + targetPanel.addClass('active').setStyle('display', 'block'); + this.addClass('active'); + } + }); + + //enable hiding of toolbar. + var panelButtons = $$('#debug-kit-toolbar .panel-tab:not(.panel-tab.icon)'); + var toolbarHide = $('hide-toolbar').set('state', 'open'); + toolbarHide.addEvent('click', function(event) { + event.stop(); + var state = this.get('state'); + if (state == 'open') { + panelButtons.setStyle('display', 'none'); + this.set('state', 'closed') + } else { + panelButtons.setStyle('display'); + this.set('state', 'open'); + } + }); + } +}); \ No newline at end of file diff --git a/plugins/debug_kit/vendors/js/prototype_debug_toolbar.js b/plugins/debug_kit/vendors/js/prototype_debug_toolbar.js new file mode 100644 index 0000000..8e1e63f --- /dev/null +++ b/plugins/debug_kit/vendors/js/prototype_debug_toolbar.js @@ -0,0 +1,95 @@ +/* SVN FILE: $Id$ */ +/** + * Debug Toolbar Javascript. Prototype 1.6.x compatible. + * + * Long description here. + * + * PHP versions 4 and 5 + * + * CakePHP : Rapid Development Framework + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +document.observe('dom:loaded', function() { + new DebugKit(); +}); + +var DebugKit = Class.create({ + + initialize: function(){ + this.toolbar(); + this.neatArray(); + }, + + toolbar: function(){ + var tabCollection = $('debug-kit-toolbar').select('li > div'); + + $('debug-kit-toolbar').select('.panel-tab > a').invoke('observe', 'click', function(e){ + e.stop(); + var targetPanel = $(e.element().hash.replace(/#/, '') + '-tab'); + if (targetPanel.hasClassName('active')) { + tabCollection.each(function(ele){ + ele.hide().removeClassName('active'); + }); + } else { + tabCollection.each(function(ele){ + ele.hide().removeClassName('active'); + if (targetPanel.id == ele.id) { + ele.setStyle({display: 'block'}).addClassName('active'); + } + }); + } + $('debug-kit-toolbar').select('.panel-tab > a').invoke('removeClassName', 'active'); + e.element().addClassName('active'); + }); + + // enable hiding of toolbar. + var panelButtons = $('debug-kit-toolbar').select('.panel-tab'); + $('hide-toolbar').observe('click', function(eve){ + eve.stop(); + panelButtons.each(function(panel){ + if (!panel.hasClassName('icon')) { + panel.toggle(); + }; + }); + }); + }, + +/** + * Create all behaviors for neat array elements + */ + neatArray: function() { + $('debug-kit-toolbar').select('.neat-array li').each(function(ele){ + var sub = ele.select('ul'); + if (sub.length > 0) { + ele.addClassName('collapsed').addClassName('expandable'); + sub.invoke('hide'); + ele.observe('click', function(eve){ + if (eve.element() == ele || eve.element().up() == ele) { + if (sub.length > 0) { + ele.toggleClassName('expanded').toggleClassName('collapsed'); + sub[0].toggle(); + } + } + }); + }; + }); + } + +}); diff --git a/plugins/debug_kit/vendors/js/yui_debug_toolbar.js b/plugins/debug_kit/vendors/js/yui_debug_toolbar.js new file mode 100644 index 0000000..91571a1 --- /dev/null +++ b/plugins/debug_kit/vendors/js/yui_debug_toolbar.js @@ -0,0 +1,126 @@ +/* SVN FILE: $Id$ */ +/** + * Debug Toolbar Javascript. YUI 2.6 compatible. + * + * Long description here. + * + * PHP versions 4 and 5 + * + * CakePHP : Rapid Development Framework + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +YAHOO.namespace('CakePHP.DebugKit'); + +YAHOO.CakePHP.DebugKit = function() { + + var Event = YAHOO.util.Event; + var Dom = YAHOO.util.Dom; + var Selector = YAHOO.util.Selector; + + + var toggle = function(el) { + Dom.setStyle(el, 'display', ((Dom.getStyle(el, 'display') == 'none') ? '' : 'none')); + }; + + var toggleClass = function(element, className) { + (Dom.hasClass(element, className)) ? Dom.removeClass(element, className) : Dom.addClass(element, className); + }; + + + + var toolbar = function() { + var tabCollection = Selector.query('#debug-kit-toolbar li > div'); + + Dom.batch(Selector.query('#debug-kit-toolbar .panel-tab > a'), function(el) { + Event.on(el, 'click', function(ev) { + Event.preventDefault(ev); + targetPanel =Dom.get(el.hash.replace(/#/, '') + '-tab'); + + if (Dom.hasClass(targetPanel, 'active')) { + Dom.batch(tabCollection, function(ele) { + toggle(ele); + Dom.removeClass(ele, 'active'); + Dom.setStyle(ele, 'display', ''); + }); + } else { + Dom.batch(tabCollection, function(ele) { + toggle(ele); + Dom.removeClass(ele, 'active'); + + if (targetPanel && targetPanel.id == ele.id) { + Dom.setStyle(ele, 'display', 'block'); + Dom.addClass(ele, 'active'); + } + }); + } + + Dom.removeClass(Selector.query('#debug-kit-toolbar .panel-tab > a'), 'active'); + Dom.addClass(el, 'active'); + }); + }); + + }; + + + + var neatArray = function() { + nodes = Selector.query('#debug-kit-toolbar .panel-content > ul.neat-array li > ul'); + + if (nodes.length > 0) { + Dom.batch(nodes, function(el) { + + var parent = el.parentNode; + + Dom.addClass(parent, 'collapsed'); + Dom.addClass(parent, 'expandable'); + toggle(nodes); + + Event.on(parent, 'click', function(ev) { + sub = Selector.query('ul', parent); + toggleClass(parent, 'expanded'); + toggleClass(parent, 'collapsed'); + toggle(sub); + }); + }); + } + }; + + var panelButtons = function() { + Event.on('hide-toolbar', 'click', function(ev) { + Event.preventDefault(ev); + + Dom.getElementsByClassName('panel-tab', 'li', 'debug-kit-toolbar', function (el) { + if (!Dom.hasClass(el, 'icon')) { + toggle(el); + } + }); + }); + }; + + return { + initialize: function() { + neatArray(); + toolbar(); + panelButtons(); + } + }; +}(); // Execute annonymous closure & return results + +YAHOO.util.Event.onDOMReady(YAHOO.CakePHP.DebugKit.initialize); \ No newline at end of file diff --git a/plugins/debug_kit/vendors/shells/benchmark.php b/plugins/debug_kit/vendors/shells/benchmark.php new file mode 100644 index 0000000..bac6d68 --- /dev/null +++ b/plugins/debug_kit/vendors/shells/benchmark.php @@ -0,0 +1,79 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.vendors.shells + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +class BenchmarkShell extends Shell { + function main() { + $url = $this->args[0]; + $this->out(sprintf('-> Testing %s', $url)); + + $n = 100; + $t = $i = 0; + if (isset($this->params['n'])) { + $n = $this->params['n']; + } + if (isset($this->params['t'])) { + $t = $this->params['t']; + } + $start = microtime(true); + while (true) { + if ($t && $t <= floor(microtime(true) - $start)) { + break; + } + if ($n <= $i) { + break; + } + file_get_contents($url); + $i++; + } + $duration = microtime(true) - $start; + $this->out('Total Requests made: ' . $i); + $this->out('Total Time elapsed: ' . $duration . '(s)'); + $this->out(round($n / $duration, 2).' req/sec'); + } + + function help() { + $out = <<out($out); + } +} + +?> \ No newline at end of file diff --git a/plugins/debug_kit/views/debug.php b/plugins/debug_kit/views/debug.php new file mode 100644 index 0000000..ed2a2a4 --- /dev/null +++ b/plugins/debug_kit/views/debug.php @@ -0,0 +1,154 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.cake.libs. + * @since CakePHP v 1.2.0.4487 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Vendor', 'DebugKit.DebugKitDebugger'); +App::import('Component', 'DebugKit.Toolbar'); +/** + * DebugView used by DebugKit + * + * @package debug_kit.views + * @todo Remove workarounds. + */ +class DebugView extends DoppelGangerView { +/** + * The old extension of the current template. + * + * @var string + */ + var $_oldExtension = null; +/** + * Overload _render to capture filenames and time actual rendering of each view file + * + * @param string $___viewFn Filename of the view + * @param array $___dataForView Data to include in rendered view + * @return string Rendered output + * @access protected + */ + function _render($___viewFn, $___dataForView, $loadHelpers = true, $cached = false) { +/* pr(array('function' => 'DebugView::_render', */ +/* 'args' => compact('__viewFn', '__dataForView', 'loadHelpers'), */ +/* 'this-output-len' => strlen($this->output), */ +/* )); */ + if (isset($this->_oldExtension) && strstr($___viewFn, '.debug_view')) { + $___viewFn = substr($___viewFn, 0, -10) . $this->_oldExtension; + } + if (!isset($___dataForView['disableTimer'])) { + DebugKitDebugger::startTimer('render_' . basename($___viewFn), sprintf(__('Rendering %s', true), Debugger::trimPath($___viewFn))); + } + + $out = parent::_render($___viewFn, $___dataForView, $loadHelpers, $cached); + + if (!isset($___dataForView['disableTimer'])) { + DebugKitDebugger::stopTimer('render_' . basename($___viewFn)); + } +/* pr(array('function' => 'DebugView::_render', */ +/* 'out-len' => strlen($out), */ +/* 'this-output-len' => strlen($this->output), */ +/* 'return' => $out ? 'not-empty' : 'empty')); */ + return $out; + } + +/** + * Renders view for given action and layout. If $file is given, that is used + * for a view filename (e.g. customFunkyView.ctp). + * Adds timers, for all subsequent rendering, and injects the debugKit toolbar. + * + * @param string $action Name of action to render for + * @param string $layout Layout to use + * @param string $file Custom filename for view + * @return string Rendered Element + */ + function render($action = null, $layout = null, $file = null) { +/* pr(array('function' => 'DebugView::render', */ +/* 'args' => compact('action', 'layout', 'file'), */ +/* 'this-output-len' => strlen($this->output), */ +/* )); */ + DebugKitDebugger::startTimer('viewRender', __('Rendering View', true)); + $out = parent::render($action, $layout, $file); +/* pr(array('function' => 'DebugView::render', */ +/* 'checkpoint' => 'parent::render', */ +/* 'out' => $out ? 'not-empty' : 'empty')); */ + DebugKitDebugger::stopTimer('viewRender'); + DebugKitDebugger::stopTimer('controllerRender'); + + if (isset($this->loaded['toolbar'])) { +/* pr(array('function' => 'DebugView::render', */ +/* 'checkpoint' => 'toolbarLoaded')); */ + $this->loaded['toolbar']->postRender(); + } + //Temporary work around to hide the SQL dump at page bottom + Configure::write('debug', 0); +/* pr(array('function' => 'DebugView::render', */ +/* 'out-len' => strlen($out), */ +/* 'this-output-len' => strlen($this->output), */ +/* 'return' => $this->output ? 'not-empty' : 'empty')); */ + return $this->output; + } + +/** + * Workaround _render() limitation in core. Which forces View::_render() for .ctp and .thtml templates + * Creates temporary extension to trick View::render() & View::renderLayout() + * + * @param string $name Action name. + * @return string + **/ + function _getViewFileName($name = null) { + $filename = parent::_getViewFileName($name); + return $this->_replaceExtension($filename); + } + +/** + * Workaround _render() limitation in core. Which forces View::_render() for .ctp and .thtml templates + * Creates temporary extension to trick View::render() & View::renderLayout() + * + * @param string $name Layout Name + * @return string + **/ + function _getLayoutFileName($name = null) { + $filename = parent::_getLayoutFileName($name); + return $this->_replaceExtension($filename); + } + +/** + * replace the Extension on a filename and set the temporary workaround extension. + * + * @param string $filename Filename to replace extension for. + * @return string + **/ + function _replaceExtension($filename) { + if (substr($filename, -3) == 'ctp') { + $this->_oldExtension = 'ctp'; + $filename = substr($filename, 0, strlen($filename) -3) . 'debug_view'; + } elseif (substr($filename, -5) == 'thtml') { + $this->_oldExtension = 'thtml'; + $filename = substr($filename, 0, strlen($filename) -5) . 'debug_view'; + } + return $filename; + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/views/elements/debug_toolbar.ctp b/plugins/debug_kit/views/elements/debug_toolbar.ctp new file mode 100644 index 0000000..a706fa3 --- /dev/null +++ b/plugins/debug_kit/views/elements/debug_toolbar.ctp @@ -0,0 +1,52 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +
+ +

+ + + +
\ No newline at end of file diff --git a/plugins/debug_kit/views/elements/empty b/plugins/debug_kit/views/elements/empty new file mode 100644 index 0000000..e69de29 diff --git a/plugins/debug_kit/views/elements/log_panel.ctp b/plugins/debug_kit/views/elements/log_panel.ctp new file mode 100644 index 0000000..0a075ce --- /dev/null +++ b/plugins/debug_kit/views/elements/log_panel.ctp @@ -0,0 +1,48 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +

+
+ $logs): ?> +

+ 0): + $headers = array(__('Time', true), __('Message', true)); + $rows = array(); + for ($i = 0; $i < $len; $i += 2): + $rows[] = array( + $logs[$i], $logs[$i + 1] + ); + endfor; + echo $toolbar->table($rows, $headers, array('title' => $logName)); + else: ?> +

+ + +
\ No newline at end of file diff --git a/plugins/debug_kit/views/elements/memory_panel.ctp b/plugins/debug_kit/views/elements/memory_panel.ctp new file mode 100644 index 0000000..df9e059 --- /dev/null +++ b/plugins/debug_kit/views/elements/memory_panel.ctp @@ -0,0 +1,43 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +

+
+ message( + __('Current Memory Use',true), + $number->toReadableSize(DebugKitDebugger::getMemoryUse()) + );?> +
+
+message( + __('Peak Memory Use', true), + $number->toReadableSize(DebugKitDebugger::getPeakMemoryUse()) + ); +?>
\ No newline at end of file diff --git a/plugins/debug_kit/views/elements/request_panel.ctp b/plugins/debug_kit/views/elements/request_panel.ctp new file mode 100644 index 0000000..8da4289 --- /dev/null +++ b/plugins/debug_kit/views/elements/request_panel.ctp @@ -0,0 +1,45 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +

+

Cake Params

+makeNeatArray($content['params']); ?> + +

$_GET

+makeNeatArray($content['get']); ?> + +

Cookie

+ + makeNeatArray($content['cookie']); ?> + +

To view Cookies, add CookieComponent to Controller + + +

+makeNeatArray($content['currentRoute']); ?> \ No newline at end of file diff --git a/plugins/debug_kit/views/elements/session_panel.ctp b/plugins/debug_kit/views/elements/session_panel.ctp new file mode 100644 index 0000000..003032a --- /dev/null +++ b/plugins/debug_kit/views/elements/session_panel.ctp @@ -0,0 +1,31 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +

+makeNeatArray($content); ?> \ No newline at end of file diff --git a/plugins/debug_kit/views/elements/sql_log_panel.ctp b/plugins/debug_kit/views/elements/sql_log_panel.ctp new file mode 100644 index 0000000..cda2d5a --- /dev/null +++ b/plugins/debug_kit/views/elements/sql_log_panel.ctp @@ -0,0 +1,40 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +

+ + $queryLog) : ?> +
+

+ +
+ + +

+ \ No newline at end of file diff --git a/plugins/debug_kit/views/elements/timer_panel.ctp b/plugins/debug_kit/views/elements/timer_panel.ctp new file mode 100644 index 0000000..1ec74d6 --- /dev/null +++ b/plugins/debug_kit/views/elements/timer_panel.ctp @@ -0,0 +1,44 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +$timers = DebugKitDebugger::getTimers(); +?> +

+

+ precision(DebugKitDebugger::requestTime(), 6)); ?> + message(__('Total Request Time:', true), $totalTime)?> +

+ + $timeInfo): + $rows[] = array( + $timeInfo['message'], + $number->precision($timeInfo['time'], 6) + ); + $headers = array(__('Message', true), __('time in seconds', true)); +endforeach; +echo $toolbar->table($rows, $headers, array('title' => 'Timers')); ?> \ No newline at end of file diff --git a/plugins/debug_kit/views/elements/variables_panel.ctp b/plugins/debug_kit/views/elements/variables_panel.ctp new file mode 100644 index 0000000..95dc9bf --- /dev/null +++ b/plugins/debug_kit/views/elements/variables_panel.ctp @@ -0,0 +1,35 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package cake + * @subpackage cake.debug_kit.views.elements + * @since + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +?> +

+viewVars; + unset($vars['debugToolbarPanels'], $vars['debugToolbarJavascript']); +?> +makeNeatArray($vars); ?> \ No newline at end of file diff --git a/plugins/debug_kit/views/helpers/fire_php_toolbar.php b/plugins/debug_kit/views/helpers/fire_php_toolbar.php new file mode 100644 index 0000000..630a707 --- /dev/null +++ b/plugins/debug_kit/views/helpers/fire_php_toolbar.php @@ -0,0 +1,79 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package debug_kit + * @subpackage debug_kit.views.helpers + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('helper', 'DebugKit.Toolbar'); +App::import('Vendor', 'DebugKit.FireCake'); + +class FirePhpToolbarHelper extends ToolbarHelper { +/** + * send method + * + * @return void + * @access protected + */ + function _send() { + $view =& ClassRegistry::getObject('view'); + $view->element('debug_toolbar', array('plugin' => 'debug_kit', 'disableTimer' => true)); + Configure::write('debug', 1); + } +/** + * makeNeatArray. + * + * wraps FireCake::dump() allowing panel elements to continue functioning + * + * @param string $values + * @return void + */ + function makeNeatArray($values) { + FireCake::info($values); + } +/** + * Create a simple message + * + * @param string $label Label of message + * @param string $message Message content + * @return void + */ + function message($label, $message) { + FireCake::log($message, $label); + } +/** + * Generate a table with FireCake + * + * @param array $rows Rows to print + * @param array $headers Headers for table + * @param array $options Additional options and params + * @return void + */ + function table($rows, $headers, $options = array()) { + $title = $headers[0]; + if (isset($options['title'])) { + $title = $options['title']; + } + array_unshift($rows, $headers); + FireCake::table($title, $rows); + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/views/helpers/html_toolbar.php b/plugins/debug_kit/views/helpers/html_toolbar.php new file mode 100644 index 0000000..fc58014 --- /dev/null +++ b/plugins/debug_kit/views/helpers/html_toolbar.php @@ -0,0 +1,150 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package debug_kit + * @subpackage debug_kit.views.helpers + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('helper', 'DebugKit.Toolbar'); + +class HtmlToolbarHelper extends ToolbarHelper { +/** + * helpers property + * + * @var array + * @access public + */ + var $helpers = array('Html', 'Javascript'); +/** + * settings property + * + * @var array + * @access public + */ + var $settings = array('format' => 'html'); +/** + * Recursively goes through an array and makes neat HTML out of it. + * + * @param mixed $values Array to make pretty. + * @param int $openDepth Depth to add open class + * @param int $currentDepth current depth. + * @return string + **/ + function makeNeatArray($values, $openDepth = 0, $currentDepth = 0) { + $className ="neat-array depth-$currentDepth"; + if ($openDepth > $currentDepth) { + $className .= ' expanded'; + } + $nextDepth = $currentDepth + 1; + $out = "
    "; + if (!is_array($values)) { + if (is_bool($values)) { + $values = array($values); + } + if (is_null($values)) { + $values = array(null); + } + } + foreach ($values as $key => $value) { + $out .= '
  • ' . $key . ''; + if ($value === null) { + $value = '(null)'; + } + if ($value === false) { + $value = '(false)'; + } + if ($value === true) { + $value = '(true)'; + } + if (empty($value) && $value != 0) { + $value = '(empty)'; + } + + if (is_object($value)) { + $value = Set::reverse($value, true); + } + + if (is_array($value) && !empty($value)) { + $out .= $this->makeNeatArray($value, $openDepth, $nextDepth); + } else { + $out .= $value; + } + $out .= '
  • '; + } + $out .= '
'; + return $out; + } +/** + * Create an HTML message + * + * @param string $label label content + * @param string $message message content + * @return string + */ + function message($label, $message) { + return sprintf('

%s %s

', $label, $message); + } +/** + * Create a table. + * + * @param array $rows Rows to make. + * @param array $headers Optional header row. + * @return string + */ + function table($rows, $headers = array()) { + $out = ''; + if (!empty($headers)) { + $out .= $this->Html->tableHeaders($headers); + } + $out .= $this->Html->tableCells($rows, array('class' => 'odd'), array('class' => 'even')); + $out .= '
'; + return $out; + } +/** + * send method + * + * @return void + * @access protected + */ + function _send() { + if (Configure::read('debug') == 0) { + return; + } + $view =& ClassRegistry::getObject('view'); + $head = $this->Html->css('/debug_kit/css/debug_toolbar'); + if (isset($view->viewVars['debugToolbarJavascript'])) { + foreach ($view->viewVars['debugToolbarJavascript'] as $script) { + if ($script) { + $head .= $this->Javascript->link($script); + } + } + } + if (preg_match('##', $view->output)) { + $view->output = preg_replace('##', $head . "\n", $view->output, 1); + } + $toolbar = $view->element('debug_toolbar', array('plugin' => 'debug_kit', 'disableTimer' => true)); + if (preg_match('##', $view->output)) { + $view->output = preg_replace('##', $toolbar . "\n", $view->output, 1); + } + } +} +?> \ No newline at end of file diff --git a/plugins/debug_kit/views/helpers/toolbar.php b/plugins/debug_kit/views/helpers/toolbar.php new file mode 100644 index 0000000..c107730 --- /dev/null +++ b/plugins/debug_kit/views/helpers/toolbar.php @@ -0,0 +1,90 @@ + + * Copyright 2006-2008, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2006-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project + * @package debug_kit + * @subpackage debug_kit.views.helpers + * @since v 1.0 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +App::import('Vendor', 'DebugKit.DebugKitDebugger'); + +class ToolbarHelper extends AppHelper { +/** + * settings property to be overloaded. Subclasses should specify a format + * + * @var array + * @access public + */ + var $settings = array(); +/** + * Construct the helper and make the backend helper. + * + * @param string $options + * @access public + * @return void + */ + function __construct($options = array()) { + $this->_myName = strtolower(get_class($this)); + if ($this->_myName !== 'toolbarhelper') { + return; + } + if (!isset($options['output'])) { + $options['output'] = 'DebugKit.HtmlToolbar'; + } + App::import('Helper', $options['output']); + $className = $options['output']; + if (strpos($options['output'], '.') !== false) { + list($plugin, $className) = explode('.', $options['output']); + } + $this->_backEndClassName = $className; + $this->helpers = array($options['output']); + } + +/** + * call__ + * + * Allows method calls on backend helper + * + * @param string $method + * @param mixed $params + * @access public + * @return void + */ + function call__($method, $params) { + if (method_exists($this->{$this->_backEndClassName}, $method)) { + return $this->{$this->_backEndClassName}->dispatchMethod($method, $params); + } + } + +/** + * postRender method + * + * Custom Callback defined in DebugView to allow helpers to modify + * View output after all rendering is complete. + * + * @return void + * @access public + */ + function postRender() { + $this->_send(); + } +} \ No newline at end of file diff --git a/views/accounts/collected.ctp b/views/accounts/collected.ctp new file mode 100644 index 0000000..0d0df56 --- /dev/null +++ b/views/accounts/collected.ctp @@ -0,0 +1,207 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Javascript + */ + +?> + + + +' . "\n"; + +$rows = array(); +$rows[] = array('Total:', ''); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * User Configuration + */ + +echo $form->create(null, array('id' => 'collected-form', + 'onsubmit' => 'return false', + 'url' => null)); + +echo $form->input("id", + array('id' => 'account-id', + 'type' => 'hidden', + 'value' => 0)); + +/* echo ''; */ + +echo $this->element('form_table', + array('class' => "item account collected entry", + //'with_name_after' => ':', + 'field_prefix' => 'Tx.', + 'fields' => array + ("account_id" => array('name' => 'Payment
Account', + 'opts' => + array('type' => 'select', + 'multiple' => 'checkbox', + 'options' => $paymentAccounts, + 'selected' => array_keys($defaultAccounts), + ), + ), + ), + )); + +echo $this->element('form_table', + array('class' => "item account collected entry", + //'with_name_after' => ':', + 'field_prefix' => 'Tx.', + 'fields' => array + ("from_date" => array('opts' => + array('type' => 'text'), + 'between' => 'BOM', + ), + "through_date" => array('opts' => + array('type' => 'text'), + 'between' => 'EOM', + ), + ), + )); + +echo $form->button('Update', + array('onclick' => 'updateEntriesGrid(); return false', + )); + +echo $form->end(); + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Entries + */ + +echo $this->element('statement_entries', array + (// Grid configuration + 'config' => array + ( + 'grid_div_id' => 'collected-entries', + 'grid_div_class' => 'text-below', + 'grid_events' => array('loadComplete' => 'onGridLoadComplete()'), + 'grid_setup' => array('hiddengrid' => true), + 'caption' => 'Collected ' . Inflector::pluralize($account['name']), + 'action' => 'collected', + 'filter' => array('ChargeEntry.account_id' => $account['id']), + 'include' => array('Amount'), + 'exclude' => array(/*'Type',*/ 'Debit', 'Credit'), + ), + )); + +?> + + + +' . "\n"; + +/* End page div */ +echo '
' . "\n"; + +?> diff --git a/views/accounts/view.ctp b/views/accounts/view.ctp new file mode 100644 index 0000000..897ab78 --- /dev/null +++ b/views/accounts/view.ctp @@ -0,0 +1,113 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Account Detail Main Section + */ + +$ledger = $account['Ledger']; +$current_ledger = $account['CurrentLedger']; + +if (isset($account['Account'])) + $account = $account['Account']; + +$rows = array(); +$rows[] = array('ID', $account['id']); +$rows[] = array('Name', $account['name']); +$rows[] = array('Type', $account['type']); +$rows[] = array('External Name', $account['external_name']); +$rows[] = array('External Account', $account['external_account']); +$rows[] = array('Comment', $account['comment']); + +echo $this->element('table', + array('class' => 'item account detail', + 'caption' => 'Account Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Account Info Box + */ + +echo '
' . "\n"; +$rows = array(); +$rows[] = array('Debits:', FormatHelper::currency($stats['debits'])); +$rows[] = array('Credits:', FormatHelper::currency($stats['credits'])); +$rows[] = array('Account Balance:', FormatHelper::currency($stats['balance'])); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Ledgers + */ + +echo $this->element('ledgers', array + (// Grid configuration + 'config' => array + ('caption' => $account['name'] . " Ledgers", + 'filter' => array('Account.id' => $account['id']), + ))); + + +/********************************************************************** + * Current Ledger + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ('grid_div_id' => 'ledger-ledger-entry-list', + 'caption' => ("Current Ledger: " . + "(". $current_ledger['name'] .")"), + 'filter' => array('Ledger.id' => $current_ledger['id']), + 'exclude' => array('Account', 'Amount', 'Cr/Dr', 'Balance', + empty($account['receipts']) ? 'Tender' : null), + 'include' => array('Debit', 'Credit', 'Sub-Total'), + ))); + + + +/********************************************************************** + * Entire Account + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ('grid_div_id' => 'account-ledger-entry-list', + 'grid_setup' => array('hiddengrid' => true), + 'caption' => "Entire Ledger", + 'filter' => array('Account.id' => $account['id']), + 'exclude' => array('Account', 'Amount', 'Cr/Dr', 'Balance', + empty($account['receipts']) ? 'Tender' : null), + 'include' => array('Debit', 'Credit', 'Sub-Total'), + ))); + + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/contacts/edit.ctp b/views/contacts/edit.ctp new file mode 100644 index 0000000..163af2f --- /dev/null +++ b/views/contacts/edit.ctp @@ -0,0 +1,403 @@ +varstore = compact('methodTypes', 'methodPreferences', + 'contactPhones', 'phoneTypes', + 'contactAddresses', + 'contactEmails'); + +//pr($this->data); +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Javascript + */ + +function contactMethodDiv($obj, $type, $legend, $values = null) { + + $div = + // BEGIN type-div + '
' . "\n" . + // BEGIN type-fieldset + '
' . "\n" . + ''.$legend.' #%{id} (%{remove})' . "\n" . + + // BEGIN source-div + '
' . "\n" . + // BEGIN source-fieldset + '
' . "\n" . + //'Source' . "\n" . + '' + ; + + if (!isset($values)) { + foreach (array('Existing', 'New') AS $sname) { + $stype = strtolower($sname); + $div .= + '' . "\n" . + ' ' . "\n" . + ' '; + } + } + $div .= "\n"; + + if (isset($values)) { + $div .= contactMethodTypeDiv($obj, $type, 'show', $values); + } + else { + $div .= contactMethodTypeDiv($obj, $type, 'existing'); + $div .= contactMethodTypeDiv($obj, $type, 'new'); + } + + $div .= + // END source-fieldset + '
' . "\n" . + // END source-div + '
' . "\n" . + + // BEGIN method-div + '
element + ('form_table', + array('class' => "item contact-{$type} entry", + 'field_prefix' => 'Contact'.ucfirst($type).'.%{id}.ContactsMethod', + 'fields' => array + ( + 'preference' => array + ('opts' => array + ('options' => $obj->varstore['methodPreferences'], + 'selected' => (isset($values) ? $values['ContactsMethod']['preference'] : null), + )), + + 'type' => array + ('opts' => array + ('options' => $obj->varstore['methodTypes'], + 'selected' => (isset($values) ? $values['ContactsMethod']['type'] : null), + )), + + 'comment' => array + ('opts' => array + ('value' => (isset($values) ? $values['ContactsMethod']['comment'] : null), + )), + + ))) . "\n" . + + // END method-div + '
' . "\n" . + + // END type-fieldset + '
' . "\n" . + // END type-div + '
' + ; + + return $div; +} + +function contactMethodTypeDiv($obj, $type, $stype, $values = null) { + + $element = 'form_table'; + + if ($type === 'phone') { + if ($stype === 'existing') { + $fields = array + ('id' => array('name' => 'Phone/Ext', + 'opts' => array('options' => $obj->varstore['contactPhones'])), + ); + } + elseif ($stype === 'new') { + $fields = array + ('type' => array('opts' => array('options' => $obj->varstore['phoneTypes'])), + 'phone' => true, + 'ext' => array('name' => "Extension"), + 'comment' => true, + ); + } + elseif ($stype === 'show') { + $element = 'table'; + $column_class = array('field', 'value'); + $rows = array + (array('Type', $values['type']), + array('Phone', $values['phone']), + array('Extension', $values['ext']), + array('Comment', $values['comment']), + ); + } + else { + die("\n\nInvalid stype ($stype)\n\n"); + } + + } + elseif ($type === 'address') { + if ($stype === 'existing') { + $fields = array + ('id' => array('name' => 'Address', + 'opts' => array('options' => $obj->varstore['contactAddresses'])), + ); + } + elseif ($stype === 'new') { + $fields = array + ('address' => true, + 'city' => true, + 'state' => true, + 'postcode' => array('name' => 'Zip Code'), + 'country' => true, + 'comment' => true, + ); + } + elseif ($stype === 'show') { + $element = 'table'; + $column_class = array('field', 'value'); + $rows = array + (array('Address', preg_replace("/\n/", "
", $values['address'])), + array('City', $values['city']), + array('State', $values['state']), + array('Zip Code', $values['postcode']), + array('Country', $values['country']), + array('Comment', $values['comment']), + ); + } + else { + die("\n\nInvalid stype ($stype)\n\n"); + } + } + elseif ($type === 'email') { + if ($stype === 'existing') { + $fields = array + ('id' => array('name' => 'Email', + 'opts' => array('options' => $obj->varstore['contactEmails'])), + ); + } + elseif ($stype === 'new') { + $fields = array + ('email' => true, + 'comment' => true, + ); + } + elseif ($stype === 'show') { + $element = 'table'; + $column_class = array('field', 'value'); + $rows = array + (array('Email', $values['email']), + array('Comment', $values['comment']), + ); + } + } + else { + die("\n\nInvalid type ($type)\n\n"); + } + + return + // BEGIN sourcetype-div + '' . "\n" . + ''; +} + + +//pr($this->data); +?> + + + +' . "\n"; + +echo $form->create('Contact', array('action' => 'edit')) . "\n"; +echo $form->input('id') . "\n"; + +echo($this->element + ('form_table', + array('class' => 'item contact detail', + 'caption' => isset($this->data['Contact']) ? 'Edit Contact' : 'New Contact', + 'fields' => array + ('first_name' => true, + 'last_name' => true, + 'middle_name' => true, + 'display_name' => true, + 'company_name' => array('name' => 'Company'), + 'id_federal' => array('name' => 'SSN'), + 'id_local' => array('name' => 'ID #'), + 'id_local_state' => array('name' => 'ID State'), + /* 'id_local_exp' => array('name' => 'ID Expiration', */ + /* 'opts' => array('empty' => true)), */ + 'comment' => true, + ))) . "\n"); + +echo $form->submit('Update') . "\n"; +?> + +
+
+ Phone Numbers + +
+
+ Add a Phone Number +
+
+
+ +
+
+ Mailing Addresses + +
+
+ Add a Mailing Address +
+
+
+ +
+ +
+ +submit('Update') . "\n"; +echo $form->submit('Cancel', array('name' => 'cancel')) . "\n"; +echo $form->end() . "\n"; +echo '' . "\n"; diff --git a/views/contacts/view.ctp b/views/contacts/view.ctp new file mode 100644 index 0000000..0803999 --- /dev/null +++ b/views/contacts/view.ctp @@ -0,0 +1,145 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Contact Detail Main Section + */ + +$phones = $contact['ContactPhone']; +$addresses = $contact['ContactAddress']; +$emails = $contact['ContactEmail']; + +if (isset($contact['Contact'])) + $contact = $contact['Contact']; + +$rows = array(); +$rows[] = array('First Name', $contact['first_name']); +$rows[] = array('Middle Name', $contact['middle_name']); +$rows[] = array('Last Name', $contact['last_name']); +$rows[] = array('Company', $contact['company_name']); +$rows[] = array('SSN', $contact['id_federal']); +$rows[] = array('ID', ($contact['id_local'] + . ($contact['id_local'] + ? " - ".$contact['id_local_state'] + : ""))); +$rows[] = array('Comment', $contact['comment']); + +echo $this->element('table', + array('class' => 'item contact detail', + 'caption' => 'Contact Details', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Contact Info Box + */ + +echo '
' . "\n"; +$rows = array(); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Phones + */ +$headers = array('Phone', 'Preference', 'Comment'); +$rows = array(); +foreach($phones AS $phone) { + $rows[] = array(FormatHelper::phone($phone['phone']) . + ($phone['ext'] ? " x".$phone['ext'] : ""), + $phone['ContactsMethod']['preference'] . " / " . + $phone['ContactsMethod']['type'] . " / " . + $phone['type'], + $phone['comment']); +} + +echo $this->element('table', + array('class' => 'item phone list', + 'caption' => 'Phone', + 'headers' => $headers, + 'rows' => $rows, + 'column_class' => $headers)); + + +/********************************************************************** + * Addresses + */ +$headers = array('Address', 'Preference', 'Comment'); +$rows = array(); +foreach($addresses AS $address) { + $rows[] = array(preg_replace("/\n/", "
\n", $address['address']) . "
\n" . + $address['city'] . ", " . + $address['state'] . " " . + $address['postcode'], + //. ? "
\n" . $address['country'], + $address['ContactsMethod']['preference'] . " / " . + $address['ContactsMethod']['type'], + $address['comment']); +} + +echo $this->element('table', + array('class' => 'item address list', + 'caption' => 'Address', + 'headers' => $headers, + 'rows' => $rows, + 'column_class' => $headers)); + + +/********************************************************************** + * Emails + */ +$headers = array('Email', 'Preference', 'Comment'); +$rows = array(); +foreach($emails AS $email) { + $rows[] = array($email['email'], + $email['ContactsMethod']['preference'] . " / " . + $email['ContactsMethod']['type'], + $email['comment']); +} + +echo $this->element('table', + array('class' => 'item email list', + 'caption' => 'Email', + 'headers' => $headers, + 'rows' => $rows, + 'column_class' => $headers)); + + +/********************************************************************** + * Customers + */ + +echo $this->element('customers', array + (// Grid configuration + 'config' => array + ('caption' => 'Related Customers', + 'filter' => array('Contact.id' => $contact['id']), + ))); + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/customers/edit.ctp b/views/customers/edit.ctp new file mode 100644 index 0000000..96dd79f --- /dev/null +++ b/views/customers/edit.ctp @@ -0,0 +1,274 @@ +varstore = compact('contactTypes', 'contacts'); + +//pr($this->data); +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Javascript + */ + +function customerContactDiv($obj, $values = null, $primary = false) { + + $div = + // BEGIN type-div + '
' . "\n" . + // BEGIN type-fieldset + '
' . "\n" . + 'Contact #%{id} (%{remove})' . "\n" . + + // BEGIN source-div + '
' . "\n" . + // BEGIN source-fieldset + '
' . "\n" . + //'Source' . "\n" . + '' + ; + + if (!isset($values)) { + foreach (array('Existing', 'New') AS $sname) { + $stype = strtolower($sname); + $div .= + '' . "\n" . + ' ' . "\n" . + ' '; + } + } + $div .= "\n"; + + if (isset($values)) { + $div .= customerContactTypeDiv($obj, 'show', $values); + } + else { + $div .= customerContactTypeDiv($obj, 'existing'); + $div .= customerContactTypeDiv($obj, 'new'); + } + + $div .= + // END source-fieldset + '
' . "\n" . + // END source-div + '
' . "\n" . + + // BEGIN contact-div + '
element + ('form_table', + array('class' => "item contact entry", + 'field_prefix' => 'Contact.%{id}.ContactsCustomer', + 'fields' => array + ( + 'Customer.primary_contact_entry' => array + ('name' => 'Primary Contact', + 'no_prefix' => true, + 'opts' => array + ('type' => 'radio', + 'options' => array('%{id}' => false), + 'value' => ($primary ? '%{id}' : 'bogus-value-to-suppress-hidden-input'), + )), + + 'type' => array + ('opts' => array + ('options' => $obj->varstore['contactTypes'], + 'selected' => (isset($values) ? $values['ContactsCustomer']['type'] : null), + )), + + 'comment' => array + ('opts' => array + ('value' => (isset($values) ? $values['ContactsCustomer']['comment'] : null), + )), + + ))) . "\n" . + + // END contact-div + '
' . "\n" . + + // END type-fieldset + '
' . "\n" . + // END type-div + '
' + ; + + return $div; +} + +function customerContactTypeDiv($obj, $stype, $values = null) { + + $element = 'form_table'; + $class = $stype; + + if ($stype === 'existing') { + $fields = array + ('id' => array('name' => 'Contact', + 'opts' => array('options' => $obj->varstore['contacts'])), + ); + } + elseif ($stype === 'new') { + $fields = array + ('first_name' => true, + 'last_name' => true, + 'middle_name' => true, + 'display_name' => true, + 'company_name' => array('name' => 'Company'), + 'id_federal' => array('name' => 'SSN'), + 'id_local' => array('name' => 'ID #'), + 'id_local_state' => array('name' => 'ID State'), + /* 'id_local_exp' => array('name' => 'ID Expiration', */ + /* 'opts' => array('empty' => true)), */ + 'comment' => true, + ); + } + elseif ($stype === 'show') { + $element = 'table'; + $class = 'detail'; + $column_class = array('field', 'value'); + $rows = array(array('First Name', $values['first_name']), + array('Last Name', $values['last_name']), + array('Company', $values['company_name']), + array('Comment', $values['comment'])); + } + else { + die("\n\nInvalid stype ($stype)\n\n"); + } + + return + // BEGIN sourcetype-div + '' . "\n" . + ''; +} + + +?> + + + +' . "\n"; + +echo $form->create('Customer', array('action' => 'edit')) . "\n"; +echo $form->input('id') . "\n"; + +echo($this->element + ('form_table', + array('class' => 'item customer detail', + 'caption' => isset($this->data['Customer']) ? 'Edit Customer' : 'New Customer', + 'fields' => array + ('name' => true, + 'comment' => true, + ))) . "\n"); + +echo $form->submit(isset($this->data['Customer']) ? 'Update' : 'Add New Customer') . "\n"; +?> + +
+
+ Contacts + +
+
+ Add a Contact +
+
+
+ +submit(isset($this->data['Customer']) ? 'Update' : 'Add New Customer') . "\n"; +echo $form->submit('Cancel', array('name' => 'cancel')) . "\n"; +echo $form->end() . "\n"; +echo '' . "\n"; diff --git a/views/customers/receipt.ctp b/views/customers/receipt.ctp new file mode 100644 index 0000000..55a12e0 --- /dev/null +++ b/views/customers/receipt.ctp @@ -0,0 +1,421 @@ + + +
+ + + + +element('customers', array + ('config' => array + ('grid_div_id' => 'customers-list', + 'grid_div_class' => 'text-below', + 'caption' => ('Select Customer'), + 'grid_setup' => array('hiddengrid' => isset($customer['id'])), + 'grid_events' => array('onSelectRow' => + array('ids' => + 'if (ids != null){onRowSelect("#"+$(this).attr("id"), ids);}'), + 'onHeaderClick' => + array('gridstate' => + 'onGridState("#"+$(this).attr("id"), gridstate)'), + ), + 'action' => 'current', + 'nolinks' => true, + 'limit' => 10, + ))); + +echo ('
' . + + '' . // END customer-selection-valid + + '' . + + '
' . "\n"); + + +echo $this->element('statement_entries', array + (// Grid configuration + 'config' => array + ( + 'grid_div_id' => 'charge-entries', + 'grid_div_class' => 'text-below', + 'grid_events' => array('loadComplete' => 'onGridLoadComplete()'), + 'grid_setup' => array('hiddengrid' => true), + 'caption' => '', + 'action' => 'unreconciled', + 'exclude' => array('Customer', 'Type', 'Debit', 'Credit'), + 'include' => array('Applied', 'Balance'), + 'remap' => array('Applied' => 'Paid'), + 'limit' => 8, + ), + )); + +echo('
' . + '' . // END customer-selection-valid + '
' . + "\n"); + +echo $form->create(null, array('id' => 'receipt-form', + 'url' => array('controller' => 'transactions', + 'action' => 'postReceipt'))); + + +echo $form->input("id", + array('id' => 'customer-id', + 'type' => 'hidden', + 'value' => 0)); + +echo $this->element('form_table', + array('class' => "item receipt transaction entry", + //'with_name_after' => ':', + 'field_prefix' => 'Transaction', + 'fields' => array + ("stamp" => array('opts' => array('type' => 'text'), + 'between' => 'Now', + ), + "comment" => array('opts' => array('size' => 50), + ), + ))); + +echo "
\n"; +echo $form->input('repeat', array('type' => 'checkbox', + 'id' => 'repeat', + 'label' => 'Enter Multiple Receipts')) . "\n"; +echo $form->submit('Generate Receipt') . "\n"; +?> + + + Payments +*/ ?> + +
+ + Add Another Payment + + +*/ ?> + +end('Generate Receipt'); ?> + +' . "\n"; // End of the dialog DIV */ ?> + +
+ + + + + + +
diff --git a/views/customers/view.ctp b/views/customers/view.ctp new file mode 100644 index 0000000..19e468f --- /dev/null +++ b/views/customers/view.ctp @@ -0,0 +1,120 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Customer Detail Main Section + */ + +$rows = array(); +$rows[] = array('Name', $customer['Customer']['name']); +$rows[] = array('Since', FormatHelper::date($since, true)); +if (!empty($until)) + $rows[] = array('Until', FormatHelper::date($until, true)); +$rows[] = array('Comment', $customer['Customer']['comment']); + +echo $this->element('table', + array('class' => 'item customer detail', + 'caption' => 'Customer Info', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Customer Info Box + */ + +echo '
' . "\n"; +$rows = array(); +$rows[] = array('Security Deposit:', FormatHelper::currency($outstandingDeposit)); +//$rows[] = array('Charges:', FormatHelper::currency($stats['charges'])); +//$rows[] = array('Payments:', FormatHelper::currency($stats['disbursements'])); +$rows[] = array('Balance Owed:', FormatHelper::currency($outstandingBalance)); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Contacts + */ + +echo $this->element('contacts', array + (// Grid configuration + 'config' => array + ('caption' => 'Customer Contacts', + 'filter' => array('Customer.id' => $customer['Customer']['id']), + 'include' => array('Type', 'Active'), + ))); + + +/********************************************************************** + * Lease History + */ + +echo $this->element('leases', array + (// Grid configuration + 'config' => array + ('caption' => 'Lease History', + 'filter' => array('Customer.id' => $customer['Customer']['id']), + 'exclude' => array('Customer'), + ))); + + +/********************************************************************** + * Customer Account History + */ + +echo $this->element('statement_entries', array + (// Grid configuration + 'config' => array + ('caption' => 'Customer Statement', + 'filter' => array('Customer.id' => $customer['Customer']['id'], + 'type !=' => 'VOID'), + 'exclude' => array('Customer'), + 'sort_column' => 'Effective', + 'sort_order' => 'DESC', + ))); + + +/********************************************************************** + * Receipt History + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ('caption' => 'Receipts', + 'filter' => array('Customer.id' => $customer['Customer']['id'], + 'Transaction.type' => 'RECEIPT', + 'Tender.id !=' => null, + //'Account.id !=' => '-AR-' + ), + 'exclude' => array('Account', 'Cr/Dr'), + 'sort_column' => 'Date', + 'sort_order' => 'DESC', + ))); + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/double_entries/view.ctp b/views/double_entries/view.ctp new file mode 100644 index 0000000..0d1bb44 --- /dev/null +++ b/views/double_entries/view.ctp @@ -0,0 +1,153 @@ +' . "\n"; + +// The two entry ids, debit and credit, are actually individual +// entries in separate accounts (each make up one of the two +// entries required for "double entry"). This, when we provide +// reconcile information, we're really providing reconcile info +// for two independent accounts. The reconciling entries, +// therefore, are those on the opposite side of the ledger in +// each account. For example, assume this "double" entry is +// +// debit: A/R credit: Cash amount: 55 +// +// Then, our accounts might look like: +// +// RENT TAX A/R CASH BANK +// ------- ------- ------- ------- ------- +// |20 | 20| | | <-- Unrelated +// | | |20 20| | <-- Unrelated +// | | | | | +// |50 | 50| | | <-- Rent paid by this entry +// | |5 5| | | <-- Tax paid by this entry +// | | |55 55| | <-- THIS ENTRY +// | | | | | +// | | | |75 75| <-- Deposit includes this entry +// | | | | | +// +// In this case, we're looking to provide reconcile information +// of A/R for (the credit side of) this entry, and also of Cash +// (for the debit side). Taking the accounts as individual +// entries, instead of the "double entry" representation in the +// database, we're actually providing information on the two +// A/R entries, 50 & 5, which are both debits, i.e. opposite +// entries to the credit of A/R. The cash account entry +// reconciles against the credit of 75. Again, this is the +// opposite entry to the debit of Cash. +// +// Thus, for our debit_ledger_id, we're reconciling against +// credits, and for our credit_ledger_id, against debits. + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * LedgerEntry Detail Main Section + */ + +$transaction = $entry['Transaction']; +$ledgers = array('debit' => $entry['DebitLedger'], + 'credit' => $entry['CreditLedger']); +$entries = array('debit' => $entry['DebitEntry'], + 'credit' => $entry['CreditEntry']); +$entry = $entry['DoubleEntry']; + +$rows = array(); +$rows[] = array('ID', $entry['id']); +$rows[] = array('Transaction', $html->link('#'.$transaction['id'], + array('controller' => 'transactions', + 'action' => 'view', + $transaction['id']))); +$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp'])); +$rows[] = array('Comment', $entry['comment']); + +echo $this->element('table', + array('class' => 'item ledger-entry detail', + 'caption' => 'Double Ledger Entry Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * LedgerEntry Info Box + */ + +/* echo '
' . "\n"; */ +/* foreach ($ledgers AS $type => $ledger) { */ +/* //pr($ledger); */ +/* if (!$ledger['Account']['trackable']) */ +/* continue; */ + +/* $applied_caption = "Transfers applied"; */ +/* $remaining_caption = "Unapplied amount"; */ + +/* $rows = array(); */ +/* $rows[] = array($applied_caption, */ +/* FormatHelper::currency($stats[$type]['amount_reconciled'])); */ +/* $rows[] = array($remaining_caption, */ +/* FormatHelper::currency($stats[$type]['amount_remaining'])); */ +/* echo $this->element('table', */ +/* array('class' => 'item summary', */ +/* 'caption' => "{$ledger['Account']['name']} Ledger Entry", */ +/* 'rows' => $rows, */ +/* 'column_class' => array('field', 'value'), */ +/* //'suppress_alternate_rows' => true, */ +/* )); */ +/* } */ + +/* echo '
' . "\n"; */ + +echo ('
' . "\n"); +foreach ($ledgers AS $type => $ledger) { + $rows = array(); + + // REVISIT : 20090816 + // Due to low priority, the ledger_entry/double_entry stuff + // is a bit piecemeal at the moment (trying to reuse old + // code as much as possible). So, LedgerEntry view is just + // redirecting here. Of course, presenting a link for the + // LedgerEntry then is, well, quite pointless. + $rows[] = array('ID', '#' . $entries[$type]['id']); +/* $rows[] = array('ID', $html->link('#' . $entries[$type]['id'], */ +/* array('controller' => 'entries', */ +/* 'action' => 'view', */ +/* $entries[$type]['id']))); */ + $rows[] = array('Account', $html->link($ledger['Account']['name'], + array('controller' => 'accounts', + 'action' => 'view', + $ledger['Account']['id']))); + $rows[] = array('Ledger', $html->link('#' . $ledger['Account']['id'] + . '-' . $ledger['sequence'], + array('controller' => 'ledgers', + 'action' => 'view', + $ledger['id']))); + $rows[] = array('Amount', FormatHelper::currency($entries[$type]['amount'])); + //$rows[] = array('Effect', $ledger['Account']['ftype'] == $type ? 'INCREASE' : 'DECREASE'); + + echo $this->element('table', + array('class' => array('item', $type, 'detail'), + 'caption' => ucfirst($type) . ' Ledger Entry', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); +} +echo ('
' . "\n"); + + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/elements/accounts.ctp b/views/elements/accounts.ctp new file mode 100644 index 0000000..6396a73 --- /dev/null +++ b/views/elements/accounts.ctp @@ -0,0 +1,21 @@ + 'Account.id', 'formatter' => 'id'); +$cols['Name'] = array('index' => 'Account.name', 'formatter' => 'longname'); +$cols['Type'] = array('index' => 'Account.type', 'formatter' => 'enum'); +$cols['Entries'] = array('index' => 'entries', 'formatter' => 'number'); +$cols['Debits'] = array('index' => 'debits', 'formatter' => 'currency'); +$cols['Credits'] = array('index' => 'credits', 'formatter' => 'currency'); +$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency'); +$cols['Comment'] = array('index' => 'Account.comment', 'formatter' => 'comment'); + +// Render the grid +$grid +->columns($cols) +->sortField('Name') +->defaultFields(array('ID', 'Name')) +->searchFields(array('Name')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Comment'))); diff --git a/views/elements/contacts.ctp b/views/elements/contacts.ctp new file mode 100644 index 0000000..6b41c25 --- /dev/null +++ b/views/elements/contacts.ctp @@ -0,0 +1,20 @@ + 'Contact.id', 'formatter' => 'id'); +$cols['Last Name'] = array('index' => 'Contact.last_name', 'formatter' => 'name'); +$cols['First Name'] = array('index' => 'Contact.first_name', 'formatter' => 'name'); +$cols['Company'] = array('index' => 'Contact.company_name', 'formatter' => 'longname'); +$cols['Type'] = array('index' => 'ContactsCustomer.type', 'formatter' => 'enum'); +$cols['Active'] = array('index' => 'ContactsCustomer.active', 'formatter' => 'enum'); +$cols['Comment'] = array('index' => 'Contact.comment', 'formatter' => 'comment'); + +// Render the grid +$grid +->columns($cols) +->sortField('Last Name') +->defaultFields(array('ID', 'Last Name', 'First Name')) +->searchFields(array('Last Name', 'First Name', 'Company')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Type', 'Active', 'Comment'))); diff --git a/views/elements/customers.ctp b/views/elements/customers.ctp new file mode 100644 index 0000000..0e25850 --- /dev/null +++ b/views/elements/customers.ctp @@ -0,0 +1,26 @@ + 'Customer.id', 'formatter' => 'id'); +$cols['Relationship'] = array('index' => 'ContactsCustomer.type', 'formatter' => 'enum'); +$cols['Name'] = array('index' => 'Customer.name', 'formatter' => 'longname'); +$cols['Last Name'] = array('index' => 'PrimaryContact.last_name', 'formatter' => 'name'); +$cols['First Name'] = array('index' => 'PrimaryContact.first_name', 'formatter' => 'name'); +$cols['Leases'] = array('index' => 'current_lease_count', 'formatter' => 'number'); +$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency'); +$cols['Comment'] = array('index' => 'Customer.comment', 'formatter' => 'comment'); + + +// Certain fields are only valid with a particular context +if (!isset($config['filter']['Contact.id'])) + $grid->invalidFields('Relationship'); + +// Render the grid +$grid +->columns($cols) +->sortField('Name') +->defaultFields(array('ID', 'Name')) +->searchFields(array('Name', 'Last Name', 'First Name')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Comment'))); diff --git a/views/elements/double_entries.ctp b/views/elements/double_entries.ctp new file mode 100644 index 0000000..fe2c5a4 --- /dev/null +++ b/views/elements/double_entries.ctp @@ -0,0 +1,116 @@ + 'Transaction.id', 'formatter' => 'id'); +$cols['Entry'] = array('index' => 'LedgerEntry.id', 'formatter' => 'id'); + +$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date'); +$cols['Effective'] = array('index' => 'LedgerEntry.effective_date', 'formatter' => 'date'); +$cols['Through'] = array('index' => 'LedgerEntry.through_date', 'formatter' => 'date'); + +$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name'); +$cols['Debit Account'] = array('index' => 'DebitAccount.name', 'formatter' => 'name'); +$cols['Credit Account'] = array('index' => 'CreditAccount.name', 'formatter' => 'name'); + +$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname'); +$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id'); +$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'name'); + +$cols['Source'] = array('index' => 'MonetarySource.name', 'formatter' => 'name'); +$cols['Comment'] = array('index' => 'LedgerEntry.comment', 'formatter' => 'comment', 'width'=>150); + +$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency'); +$cols['Debit'] = array('index' => 'debit', 'formatter' => 'currency'); +$cols['Credit'] = array('index' => 'credit', 'formatter' => 'currency'); + +$cols['Last Payment'] = array('index' => 'last_paid', 'formatter' => 'date'); +$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency'); +$cols['Sub-Total'] = array('index' => 'subtotal-LedgerEntry.amount', 'formatter' => 'currency', 'sortable' => false); + + +if (isset($transaction_id) || isset($reconcile_id)) + $grid->invalidFields('Transaction'); + +if (!isset($collected_account_id)) + $grid->invalidFields('Last Payment'); + +if (isset($account_ftype) || isset($ledger_id) || isset($account_id) || isset($ar_account) || isset($customer_id)) + $grid->invalidFields(array('Debit Account', 'Credit Account')); +else + $grid->invalidFields('Account'); + +if (isset($no_account) || $group_by_tx || isset($collected_account_id)) + $grid->invalidFields(array('Account', 'Debit Account', 'Credit Account')); + +if (isset($ledger_id) || isset($account_id) || isset($ar_account) || isset($customer_id)) { + $grid->invalidFields('Amount'); + $cols['Sub-Total']['index'] = 'subtotal-balance'; +} else { + $grid->invalidFields(array('Debit', 'Credit')); + $cols['Sub-Total']['index'] = 'subtotal-LedgerEntry.amount'; +} + +// group_by_tx SHOULD wipe out Customer, but the reality +// is that it works good at the present, so we'll leave it. +if (isset($lease_id) || isset($customer_id)) + $grid->invalidFields(array('Customer')); + +if (isset($lease_id) || $group_by_tx) + $grid->invalidFields(array('Lease', 'Unit')); + +if (!isset($reconcile_id) && !isset($collected_account_id)) + $grid->invalidFields('Applied'); +else + $cols['Sub-Total']['index'] = 'subtotal-applied'; + +if (isset($account_ftype) || isset($collected_account_id)) + $grid->invalidFields('Sub-Total'); + + +// Now that columns are defined, establish basic grid parameters +$grid +->columns($cols) +->sortField('Date') +->defaultFields(array('Entry', 'Date', 'Amount', 'Credit', 'Debit')); + + +if (!isset($config['rows']) && !isset($collected_account_id)) { + $config['action'] = 'ledger'; + $grid->limit(50); +} + +if (isset($reconcile_id)) { + $config['action'] = 'reconcile'; + $grid->customData(compact('reconcile_id'))->limit(20); +} + +if (isset($collected_account_id)) { + $config['action'] = 'collected'; + $account_id = $collected_account_id; + $grid->limit(50); + $grid->sortField('Last Payment'); +} + +if (isset($entry_ids)) + $grid->id_list($entry_ids); + +// Set up search fields if requested by caller +if (isset($searchfields)) + $grid->searchFields(array('Customer', 'Unit')); + +// Include custom data +$grid->customData(compact('ledger_id', 'account_id', 'ar_account', + 'account_type', 'account_ftype', 'monetary_source_id', + 'customer_id', 'lease_id', 'transaction_id', 'group_by_tx')); + +// Render the grid +$grid +->render($this, isset($config) ? $config : null, + array('Transaction', 'Entry', 'Date', 'Effective', 'Last Payment', + 'Account', 'Debit Account', 'Credit Account', + 'Customer', 'Unit', + 'Comment', + 'Amount', 'Debit', 'Credit', + 'Applied', 'Sub-Total') + ); diff --git a/views/elements/email/html/default.ctp b/views/elements/email/html/default.ctp new file mode 100644 index 0000000..a217c80 --- /dev/null +++ b/views/elements/email/html/default.ctp @@ -0,0 +1,31 @@ + + ' . $line . '

'; +endforeach; +?> \ No newline at end of file diff --git a/views/elements/email/text/default.ctp b/views/elements/email/text/default.ctp new file mode 100644 index 0000000..70e05eb --- /dev/null +++ b/views/elements/email/text/default.ctp @@ -0,0 +1,25 @@ + + \ No newline at end of file diff --git a/views/elements/form_table.ctp b/views/elements/form_table.ctp new file mode 100644 index 0000000..7394f83 --- /dev/null +++ b/views/elements/form_table.ctp @@ -0,0 +1,129 @@ + $config) { + if (isset($config['before'])) + $include_before = true; +} + +$include_between = false; +if (isset($between) && $between) + $include_between = true; +foreach ($fields AS $field => $config) { + if (isset($config['between'])) + $include_between = true; +} + +$include_after = false; +if (isset($after) && $after) + $include_after = true; +foreach ($fields AS $field => $config) { + if (isset($config['after'])) + $include_after = true; +} + + +$column_class = array(); +if ($include_before) + $column_class[] = 'before'; +$column_class[] = 'field'; +if ($include_between) + $column_class[] = 'between'; +$column_class[] = 'value'; +if ($include_after) + $column_class[] = 'after'; + +$rows = array(); +foreach ($fields AS $field => $config) { + if (!isset($config)) + continue; + + if (is_bool($config) && !$config) + continue; + + if (is_bool($config) && $config) + $config = array(); + + if (!isset($config['name'])) + $config['name'] = implode(' ', array_map('ucfirst', explode('_', $field))); + if (!isset($config['opts'])) + $config['opts'] = null; + + if (isset($config['prefix']) && !isset($config['no_prefix'])) + $field = $config['prefix'] . '.' . $field; + elseif (isset($field_prefix) && !isset($config['no_prefix'])) + $field = $field_prefix . '.' . $field; + + if (!isset($config['opts']['label'])) + $config['opts']['label'] = false; + if (!isset($config['opts']['div'])) + $config['opts']['div'] = false; + + $cells = array(); + if ($include_before) { + if (isset($config['before'])) + $cells[] = $config['before']; + elseif (isset($before) && $before) + $cells[] = $before; + else + $cells[] = null; + } + + $name = $config['name']; + if (isset($config['with_name_before'])) + $name = $config['with_name_before'] . $name; + elseif (isset($with_name_before)) + $name = $with_name_before . $name; + if (isset($config['with_name_after'])) + $name = $name . $config['with_name_after']; + elseif (isset($with_name_after)) + $name = $name . $with_name_after; + $cells[] = $name; + + if ($include_between) { + if (isset($config['between'])) + $cells[] = $config['between']; + elseif (isset($between) && $between) + $cells[] = $between; + else + $cells[] = null; + } + + $value = $form->input($field, $config['opts']); + if (isset($config['with_value_before'])) + $value = $config['with_value_before'] . $value; + elseif (isset($with_value_before)) + $value = $with_value_before . $value; + if (isset($config['with_value_after'])) + $value = $value . $config['with_value_after']; + elseif (isset($with_value_after)) + $value = $valeu . $with_value_after; + $cells[] = $value; + + if ($include_after) { + if (isset($config['after'])) + $cells[] = $config['after']; + elseif (isset($after) && $after) + $cells[] = $after; + else + $cells[] = null; + } + + $rows[] = $cells; +} + +echo $this->element('table', + compact('id', 'class', 'caption', 'headers', + 'rows', 'row_class', 'suppress_alternate_rows', + 'column_class') + ); diff --git a/views/elements/jqGrid.ctp b/views/elements/jqGrid.ctp new file mode 100644 index 0000000..7d86d6e --- /dev/null +++ b/views/elements/jqGrid.ctp @@ -0,0 +1,318 @@ +'.$this->pageTitle.''; + +if (!isset($limit)) + $limit = 20; + +if (!isset($limitOptions)) { + $limitOptions = array($limit); + if ($limit <= 10) + array_push($limitOptions, 2, 5); + if ($limit <= 30) + array_push($limitOptions, 10, 25); + if ($limit > 10) + array_push($limitOptions, 25, 50, 200); + if ($limit > 20) + array_push($limitOptions, 500); +} +sort($limitOptions, SORT_NUMERIC); +$limitOptions = array_unique($limitOptions, SORT_NUMERIC); +//$limitOptions[] = 'ALL'; // Would be nice... jqGrid shows 'NaN of NaN' + +if (!isset($height)) + $height = 'auto'; + +if (!isset($controller)) + $controller = $this->params['controller']; + +if (!isset($grid_div_class)) + $grid_div_class = ''; +$grid_div_class = 'item grid list' . ($grid_div_class ? ' '.$grid_div_class : ''); + +if (!isset($grid_div_id)) + $grid_div_id = $controller . '-list'; + +if (!isset($grid_id)) + $grid_id = $grid_div_id . '-jqGrid'; + +if (!isset($search_fields)) + $search_fields = array(); + +if (!isset($grid_events)) + $grid_events = array(); + +if (!isset($grid_setup)) + $grid_setup = array(); + +// Do some prework to bring in the appropriate libraries +$html->css('ui.jqgrid', null, null, false); +$javascript->link('jqGrid/grid.locale-en', false); +$javascript->link('jqGrid/jquery.jqGrid.min', false); +$javascript->link('pmgr_jqGrid', false); + + +// Define the URL to fetch data from. +// To prevent having to keep the controller and the view +// in sync on which fields need to be queried by the +// controller in order to be accessible to the view, +// we'll just pass the desired fields to the controller +// as part of the data fetch. +$url = $html->url(array('controller' => $controller, + 'action' => 'gridData', + )); + +// Create extra parameters that jqGrid will pass to our +// controller whenever data is requested. +// 'fields' will allow the controller to return only the +// requested fields, and in the right order. +$postData = array(); +$postData['fields'] = array_map(create_function('$col', + 'return $col["index"];'), + array_values($jqGridColumns)); + +// Determine if we're to be using a custom list, or if +// the data will simply be action based. +if (isset($custom_ids)) { + if (!isset($action)) + $action = 'idlist'; + $postData['idlist'] = $custom_ids; +} + +if (isset($nolinks)) + $postData['nolinks'] = true; + +// 'action' will ensure that the controller does the right thing +// 'filter' allows the app controller to automagic filter +// 'custom' is for use solely by derived controllers +$postData['action'] = isset($action) ? $action : null; +$postData['filter'] = isset($filter) ? $filter : null; +$postData['custom'] = isset($custom_post_data) ? $custom_post_data : null; + + +// Perform column customizations. +// This will largely be based off of the 'formatter' parameter, +// but could be on any pertinent condition. +foreach ($jqGridColumns AS $header => &$col) { + $default = array(); + + // Make sure every column has a name + $default['name'] = preg_replace("/\./", '-', $col['index']); + $default['force'] = isset($col['forcewidth']) ? $col['forcewidth'] : null; + + // Perform customization based on formatter + if (isset($col['formatter'])) { + if ($col['formatter'] === 'id') { + // Use our custom formatting for ids + $col['formatter'] = array('--special' => 'idFormatter'); + $default['width'] = 50; + $default['align'] = 'center'; + + // For IDs, force the width by default, + // unless otherwise instructed NOT to. + if (!isset($default['force'])) + $default['force'] = true; + } + elseif ($col['formatter'] === 'number') { + $default['width'] = 60; + $default['align'] = 'right'; + + // No special formatting for number + unset($col['formatter']); + } + elseif ($col['formatter'] === 'currency') { + // Use our custom formatting for currency + $col['formatter'] = array('--special' => 'currencyFormatter'); + $default['width'] = 85; + $default['align'] = 'right'; + } + elseif ($col['formatter'] === 'date') { + $default['formatoptions'] = array('newformat' => 'm/d/Y'); + $default['width'] = 95; + $default['align'] = 'center'; + } + elseif (preg_match("/^(long|short)?name$/", + $col['formatter'], $matches)) { + $default['width'] = 100; + if (!empty($matches[1]) && $matches[1] === 'long') + $default['width'] *= 1.5; + if (!empty($matches[1]) && $matches[1] === 'short') + $default['width'] *= 0.7; + + // No special formatting for name + unset($col['formatter']); + } + elseif ($col['formatter'] === 'enum') { + $default['width'] = 60; + //$default['align'] = 'right'; + + // No special formatting for enum + unset($col['formatter']); + } + elseif ($col['formatter'] === 'comment') { + $default['width'] = 150; + $default['sortable'] = false; + + // No special formatting for comment + unset($col['formatter']); + } + // else just let the formatter pass through untouched + + // Just a rough approximation to ensure columns + // are wide enough to fully display their header. + $min_width = strlen($header) * 10; + if ((!isset($default['width']) || $default['width'] < $min_width) && !$default['force']) + $default['width'] = $min_width; + } + + $col = array_merge($default, $col); +} + +// Set the default sort column +if (isset($sort_column)) { + $sortname = $jqGridColumns[$sort_column]; +} else { + reset($jqGridColumns); + $sortname = current($jqGridColumns); +} +$sortname = $sortname['index']; + +// Set the default sort order +if (isset($sort_order)) { + $sortorder = $sort_order; +} else { + $sortorder = 'ASC'; +} + +$debug = !empty($this->params['dev']); +if ($debug) + $caption .= ' :: '; + +$caption .= (''); + +foreach (array_merge(array('loadComplete' => '', 'loadError' => ''), + $grid_events) AS $event => $statement) { + $params = ''; + if (is_array($statement)) { + $params = key($statement); + $statement = current($statement); + } + + if ($event == 'loadComplete' && $debug) { + $grid_events[$event] = + array('--special' => "function($params) {url=jQuery('#{$grid_id}').getGridParam('url');url=url+'/debug:1?'; pd=jQuery('#{$grid_id}').getPostData();$.each(pd,function(i){ url+=i+'='+escape(pd[i])+'&'; }); jQuery('#{$grid_id}-query').html('Grid Query
'); $statement;}"); + } + elseif ($event == 'loadError' && $debug) { + $grid_events[$event] = + array('--special' => "function($params) {url=jQuery('#{$grid_id}').getGridParam('url');url=url+'/debug:1?'; pd=jQuery('#{$grid_id}').getPostData();$.each(pd,function(i){ url+=i+'='+escape(pd[i])+'&'; }); jQuery('#{$grid_id}-query').html('Grid Error Query
'); $statement;}"); + } + elseif ($event == 'loadComplete' && !$debug) { + $grid_events[$event] = + array('--special' => "function($params) {jQuery('#{$grid_id}-error').hide(); $statement;}"); + } + elseif ($event == 'loadError' && !$debug) { + $grid_events[$event] = + array('--special' => "function($params) {jQuery('#{$grid_id}-error').show(); $statement;}"); + } + else { + $grid_events[$event] = + array('--special' => "function($params) { $statement; }"); + } +} + + +// Configure the grid setup, giving priority to user defined parameters +$jqGrid_setup = array_merge + (array('mtype' => 'GET', + 'datatype' => 'xml', + 'url' => $url, + // Since postData is a complex structure (an array), we'll + // need to serialize it first for transport over HTTP. + 'postData' => array('post' => serialize($postData)), + 'colNames' => array_keys($jqGridColumns), + 'colModel' => array('--special' => $jqGridColumns), + 'height' => $height, + 'rowNum' => $limit, + 'rowList' => $limitOptions, + 'sortname' => $sortname, + 'sortorder' => $sortorder, + 'caption' => $caption, + 'viewrecords' => true, + 'gridview' => true, + 'pager' => $grid_id.'-pager', + ), + $grid_events, + $grid_setup + ); +//pr(compact('grid_setup', 'jqGrid_setup')); + +// OK, now that everything is in place, get out of PHP mode, +// and add the javascript code (along with a touch of HTML) +// to kick this thing off. +?> + +
+
+
+ + + 0) { + echo('
Search By:
' . "\n"); + echo(''. + 'Enable Autosearch' . + '
' . "\n"); + + foreach ($search_fields AS $field) { + echo($field . "
\n"); + echo('' . "\n"); + echo('
' . "\n"); + } + echo('
' . "\n"); + echo('' . "\n"); + echo('
' . "\n"); + echo('
' . "\n"); + } + +// Finally, back to HTML mode, and end the grid DIV we created +?> +
diff --git a/views/elements/leases.ctp b/views/elements/leases.ctp new file mode 100644 index 0000000..cdd4602 --- /dev/null +++ b/views/elements/leases.ctp @@ -0,0 +1,36 @@ + 'Lease.id', 'hidden' => true); +$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id'); +$cols['Unit'] = array('index' => 'Unit.name', 'width' => '50', 'align' => 'center'); +$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname'); +$cols['Rent'] = array('index' => 'Lease.rent', 'formatter' => 'currency', 'hiddenz' => true); +$cols['Deposit'] = array('index' => 'Lease.deposit', 'formatter' => 'currency', 'hiddenz' => true); +$cols['Signed'] = array('index' => 'Lease.lease_date', 'formatter' => 'date'); +$cols['Move-In'] = array('index' => 'Lease.movein_date', 'formatter' => 'date'); +$cols['Move-Out'] = array('index' => 'Lease.moveout_date', 'formatter' => 'date'); +$cols['Closed'] = array('index' => 'Lease.close_date', 'formatter' => 'date'); +$cols['Paid-Thru'] = array('index' => 'Lease.paid_through_date', 'formatter' => 'date'); +$cols['Status'] = array('index' => 'status', 'formatter' => 'enum', 'width' => 100); +$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency'); +$cols['Comment'] = array('index' => 'Lease.comment', 'formatter' => 'comment'); + +if (!empty($this->params['action'])) { + if ($this->params['action'] === 'closed') + $grid->invalidFields(array('Paid-Thru', 'Status')); + elseif ($this->params['action'] === 'active') + $grid->invalidFields(array('Closed')); + elseif ($this->params['action'] === 'delinquent') + $grid->invalidFields(array('Closed')); +} + +// Render the grid +$grid +->columns($cols) +->sortField('LeaseID') +->defaultFields(array('LeaseID', 'Lease')) +->searchFields(array('Customer', 'Unit')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Signed', 'Status', 'Comment'))); diff --git a/views/elements/ledger_entries.ctp b/views/elements/ledger_entries.ctp new file mode 100644 index 0000000..d41c7d2 --- /dev/null +++ b/views/elements/ledger_entries.ctp @@ -0,0 +1,30 @@ + 'Transaction.id', 'formatter' => 'id'); +$cols['Entry'] = array('index' => 'LedgerEntry.id', 'formatter' => 'id'); +$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date'); + +$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name'); +$cols['Cr/Dr'] = array('index' => 'LedgerEntry.crdr', 'formatter' => 'enum'); +$cols['Tender'] = array('index' => 'Tender.name', 'formatter' => 'longname'); +$cols['Comment'] = array('index' => 'LedgerEntry.comment', 'formatter' => 'comment', 'width'=>150); + +$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency'); +$cols['Debit'] = array('index' => 'debit', 'formatter' => 'currency'); +$cols['Credit'] = array('index' => 'credit', 'formatter' => 'currency'); +$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency'); +$cols['Sub-Total'] = array('index' => 'subtotal-balance', 'formatter' => 'currency', 'sortable' => false); + + +// Render the grid +$grid +->limit(50) +->columns($cols) +->sortField('Date') +->defaultFields(array('Entry', 'Date', 'Amount')) +->searchFields(array('Customer', 'Unit')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Debit', 'Credit', 'Balance', 'Sub-Total', 'Comment'))); + diff --git a/views/elements/ledgers.ctp b/views/elements/ledgers.ctp new file mode 100644 index 0000000..d20ce13 --- /dev/null +++ b/views/elements/ledgers.ctp @@ -0,0 +1,23 @@ + 'id_sequence', 'formatter' => 'id'); +$cols['Name'] = array('index' => 'Ledger.name', 'formatter' => 'name'); +$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'longname'); +$cols['Open Date'] = array('index' => 'PriorCloseTransaction.stamp', 'formatter' => 'date'); +$cols['Close Date'] = array('index' => 'CloseTransaction.stamp', 'formatter' => 'date'); +$cols['Comment'] = array('index' => 'Ledger.comment', 'formatter' => 'comment'); +$cols['Entries'] = array('index' => 'entries', 'formatter' => 'number'); +$cols['Debits'] = array('index' => 'debits', 'formatter' => 'currency'); +$cols['Credits'] = array('index' => 'credits', 'formatter' => 'currency'); +$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency'); + +// Render the grid +$grid +->columns($cols) +->sortField('ID', 'ASC') +->defaultFields(array('ID', 'Name', 'Account')) +->searchFields(array('Account', 'Comment')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Open Date', 'Comment'))); diff --git a/views/elements/maps.ctp b/views/elements/maps.ctp new file mode 100644 index 0000000..4b467c1 --- /dev/null +++ b/views/elements/maps.ctp @@ -0,0 +1,19 @@ + 'Map.id', 'formatter' => 'id'); +$cols['Name'] = array('index' => 'Map.name', 'formatter' => 'longname'); +$cols['Site Area'] = array('index' => 'SiteArea.name', 'formatter' => 'longname'); +$cols['Width'] = array('index' => 'Map.width', 'formatter' => 'number'); +$cols['Depth'] = array('index' => 'Map.depth', 'formatter' => 'number'); +$cols['Comment'] = array('index' => 'Map.comment', 'formatter' => 'comment'); + +// Render the grid +$grid +->columns($cols) +->sortField('Name') +->defaultFields(array('ID', 'Name')) +->searchFields(array('Name')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array())); diff --git a/views/elements/sidemenu.ctp b/views/elements/sidemenu.ctp new file mode 100644 index 0000000..c1e8958 --- /dev/null +++ b/views/elements/sidemenu.ctp @@ -0,0 +1,24 @@ +' . $item['name'] . '' . "\n"); + elseif (isset($item['hr'])) + echo('
' . "\n"); + elseif (isset($item['url'])) + echo('
' + . $html->link($item['name'], $item['url'], + isset($item['htmlAttributes']) ? $item['htmlAttributes'] : null, + isset($item['confirmMessage']) ? $item['confirmMessage'] : null, + isset($item['escapeTitle']) ? $item['escapeTitle'] : null) + + . '
' . "\n"); +} diff --git a/views/elements/statement_entries.ctp b/views/elements/statement_entries.ctp new file mode 100644 index 0000000..67fafd8 --- /dev/null +++ b/views/elements/statement_entries.ctp @@ -0,0 +1,48 @@ + 'Transaction.id', 'formatter' => 'id'); +$cols['Entry'] = array('index' => 'StatementEntry.id', 'formatter' => 'id'); + +$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date'); +$cols['Effective'] = array('index' => 'StatementEntry.effective_date', 'formatter' => 'date'); +$cols['Through'] = array('index' => 'StatementEntry.through_date', 'formatter' => 'date'); + +$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname'); +$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id'); +$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'shortname'); + +$cols['Comment'] = array('index' => 'StatementEntry.comment', 'formatter' => 'comment', 'width'=>150); + +$cols['Type'] = array('index' => 'StatementEntry.type', 'formatter' => 'enum', 'width'=>120); +$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name'); +$cols['Debit'] = array('index' => 'charge', 'formatter' => 'currency'); +$cols['Credit'] = array('index' => 'disbursement', 'formatter' => 'currency'); + +$cols['Amount'] = array('index' => "StatementEntry.amount", 'formatter' => 'currency'); +$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency'); +// 'balance' is already in use as part of charge/disbursement/balance. +// 'unapplied' isn't quite the right term, but it's not customer visible. +$cols['Balance'] = array('index' => "unapplied", 'formatter' => 'currency'); +$cols['Sub-Total'] = array('index' => 'subtotal-balance', 'formatter' => 'currency', 'sortable' => false); + + +if (isset($subtotal_column)) + $cols['Sub-Total']['index'] = + 'subtotal-' . $cols[$subtotal_column]['index']; + +// Include custom data +$grid->customData(compact('statement_entry_id')); + +// Render the grid +$grid +->columns($cols) +->sortField('Date') +->defaultFields(array('Entry', 'Date', 'Charge', 'Payment')) +->searchFields(array('Customer', 'Unit')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Through', 'Lease', + 'Amount', 'Applied', 'Balance', 'Sub-Total', + 'Comment'))); + diff --git a/views/elements/table.ctp b/views/elements/table.ctp new file mode 100644 index 0000000..aef240a --- /dev/null +++ b/views/elements/table.ctp @@ -0,0 +1,89 @@ + tag. + // tableCells, however, does not have such ability :-/ + // Therefore, the row class(es) get replicated to each + // cell within the row. + + foreach ($rows AS $r => &$row) { + foreach ($row AS $c => $col) { + $cell_class = implode(" ", array_merge(isset( $row_class[$r]) ? $row_class[$r] : array(), + isset($column_class[$c]) ? $column_class[$c] : array())); + if ($cell_class) + $row[$c] = array($col, array('class' => $cell_class)); + } + } + + // Allow user to specify a list of classes + if (isset($class) && is_array($class)) + $class = implode(' ', $class); + + // OK, output the table HTML + echo('' . "\n"); + + if (isset($caption)) + echo(' ' . $caption . '' . "\n"); + + if (isset($headers) && is_array($headers)) { + echo(' ' . "\n"); + echo $html->tableHeaders($headers) . "\n"; + echo(' ' . "\n"); + } + + echo(' ' . "\n"); + echo $html->tableCells($rows, + $suppress_alternate_rows ? null : array('class' => "oddrow"), + $suppress_alternate_rows ? null : array('class' => "evnrow"), + false, false) . "\n"; + echo(' ' . "\n"); + + echo('' . "\n"); +} diff --git a/views/elements/tenders.ctp b/views/elements/tenders.ctp new file mode 100644 index 0000000..e78f20f --- /dev/null +++ b/views/elements/tenders.ctp @@ -0,0 +1,21 @@ + 'Tender.id', 'formatter' => 'id'); +$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date'); +$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname'); +$cols['Item'] = array('index' => 'Tender.name', 'formatter' => 'longname'); +$cols['Type'] = array('index' => 'TenderType.name', 'formatter' => 'name'); +$cols['Comment'] = array('index' => 'Tender.comment', 'formatter' => 'comment'); +$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency'); +$cols['Sub-Total'] = array('index' => 'subtotal-LedgerEntry.amount', 'formatter' => 'currency'); + +// Render the grid +$grid +->columns($cols) +->sortField('Date') +->defaultFields(array('Date', 'Name', 'Amount')) +->searchFields(array('Name', 'Type')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Sub-Total'))); diff --git a/views/elements/transactions.ctp b/views/elements/transactions.ctp new file mode 100644 index 0000000..dcd0240 --- /dev/null +++ b/views/elements/transactions.ctp @@ -0,0 +1,20 @@ + 'Transaction.id', 'formatter' => 'id'); +$cols['Type'] = array('index' => 'Transaction.type', 'formatter' => 'enum'); +//$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname'); +$cols['Timestamp'] = array('index' => 'Transaction.stamp', 'formatter' => 'date'); +$cols['Amount'] = array('index' => 'Transaction.amount', 'formatter' => 'currency'); +$cols['entries'] = array('index' => 'entries', 'formatter' => 'number'); +$cols['Comment'] = array('index' => 'Transaction.comment', 'formatter' => 'comment'); + +// Render the grid +$grid +->columns($cols) +->sortField('Timestamp') +->defaultFields(array('ID', 'Timestamp')) +->searchFields(array('Type', 'Comment')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Comment'))); diff --git a/views/elements/units.ctp b/views/elements/units.ctp new file mode 100644 index 0000000..b5b5220 --- /dev/null +++ b/views/elements/units.ctp @@ -0,0 +1,23 @@ + 'Unit.sort_order', 'hidden' => true); +$cols['Walk'] = array('index' => 'Unit.walk_order', 'formatter' => 'number'); +$cols['ID'] = array('index' => 'Unit.id', 'formatter' => 'id'); +$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'shortname'); +$cols['Size'] = array('index' => 'UnitSize.name', 'formatter' => 'shortname'); +$cols['Rent'] = array('index' => 'Unit.rent', 'formatter' => 'currency'); +$cols['Deposit'] = array('index' => 'Unit.deposit', 'formatter' => 'currency'); +$cols['Status'] = array('index' => 'Unit.status', 'formatter' => 'name'); // We have enough real estate +$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency'); +$cols['Comment'] = array('index' => 'Unit.comment', 'formatter' => 'comment'); + +// Render the grid +$grid +->columns($cols) +->sortField('Sort') +->defaultFields(array('Sort', 'ID', 'Unit')) +->searchFields(array('Unit', 'Size', 'Status')) +->render($this, isset($config) ? $config : null, + array_diff(array_keys($cols), array('Walk', 'Deposit', 'Comment'))); diff --git a/views/empty.ctp b/views/empty.ctp new file mode 100644 index 0000000..3888feb --- /dev/null +++ b/views/empty.ctp @@ -0,0 +1,5 @@ +currency() function results in the clever, + // but problematic, use of cents for amounts less than $1. For + // example, 50 cents is shown as '50c', not '$0.50'. We want to + // keep everything in terms of dollars, especially for the cases + // where this result is placed into a form for input. 50 cents + // will end up as 50 dollars upon submission :-( + $currency = self::$number->format + (abs($amount), + array('places' => 2, + 'before' => $spans ? '' : (isset($dollar_sign) ? $dollar_sign : '$'), + 'after' => $spans ? '' : null, + )); + + if ($amount < 0) + $currency = '(' . $currency . ')'; + + if ($spans) + return ('$' . + '' . $currency . ''); + + return $currency; + } + + function date($date, $age = false, $class = null, $time = false) { + if (!$date) return null; + + if (empty($class)) + $class = ''; + + if ($time) + $date_html = self::$time->nice($date); + else + $date_html = self::$time->format('m/d/Y', $date); + + $date_html = ''.$date_html.''; + + if ($age) { + $date_html .= ' (' . self::age($date, $class, true, $time ? 0 : 60*60*24) . ')'; + $date_html = ''.$date_html.''; + } + + return $date_html; + } + + function datetime($datetime, $age = false, $class = null) { + return self::date($datetime, $age, $class, true); + } + + function phone($phone, $ext = null) { + if (!isset($phone)) + return null; + + $phone = preg_replace("/\D/", "", $phone); + if(strlen($phone) == 7) + $phone = preg_replace("/(\d{3})(\d{4})/", "$1-$2", $phone); + elseif(strlen($phone) == 10) + $phone = preg_replace("/(\d{3})(\d{3})(\d{4})/", "$1-$2-$3", $phone); + + if ($ext) + $phone .= ' x' . $ext; + + return $phone; + } + + function comment($comment) { + if (isset($comment) && is_array($comment)) { + foreach (array_keys($comment) AS $k) { + if (!$comment[$k]) + unset($comment[$k]); + } + return implode('; ', $comment); + } + + return $comment; + } + + function age($datetime, $class, $suffix = false, $min_span = 0) { + if (!isset($datetime)) + return null; + + if (empty($class)) + $class = ''; + + $now = time(); + $seconds = self::$time->fromString($datetime); + $backwards = ($seconds > $now); + + $timefrom = $backwards ? $now : $seconds; + $timeto = $backwards ? $seconds : $now; + $span = $timeto - $timefrom; + + //pr(compact('now', 'seconds', 'backwards', 'timefrom', 'timeto', 'span', 'min_span')); + + // If now, just use 'now' + if ($span === 0) { + $approx = 0; + $unit = 'now'; + } + + // Display seconds if under 45 seconds + if ($span < 45 && $span >= $min_span) { + $approx = round($span); + $unit = 'second'; + } + + // Display minutes if under 45 minutes + if (!isset($approx)) { + $unit = 'minute'; + $span /= 60; $min_span /= 60; + if ($span < 45 && ($span >= $min_span || $min_span <= 1)) + $approx = round($span); + } + + // Display hours if under 18 hours + if (!isset($approx)) { + $unit = 'hour'; + $span /= 60; $min_span /= 60; + if ($span < 18 && ($span >= $min_span || $min_span <= 1)) + $approx = round($span); + } + + // Display days if under 6.5 days + if (!isset($approx)) { + $unit = 'day'; + $span /= 24; $min_span /= 24; + if ($span < 6.5 && ($span >= $min_span || $min_span <= 1)) + $approx = round($span); + } + + // Display weeks if less than 8 weeks + if (!isset($approx)) { + $unit = 'week'; + $span /= 7; $min_span /= 7; + if ($span < 8 && ($span >= $min_span || $min_span <= 1)) + $approx = round($span); + } + + // Display months if less than 20 months + if (!isset($approx)) { + $unit = 'month'; + $span /= 365.2425 / (7*12); $min_span /= 365.2425 / (7*12); + if ($span < 20 && ($span >= $min_span || $min_span <= 1)) { + $approx = round($span); + // Months are from 28-31 days. If it's too + // close to being an exact month, just fudge + // by saying the result is 'about' N months + // instead of 'almost' or 'over' N months, + // since we can't be accurate on this without + // taking into account the day of the week. + if ((abs($span - $approx) * (365.2425 / 12)) < 3) + $relative = 'about'; + } + } + + // Otherwise, just display years + if (!isset($approx)) { + $unit = 'year'; + $span /= 12; $min_span /= 12; + $approx = round($span); + } + + //pr(compact('span', 'min_span', 'approx', 'unit')); + + if ($approx == 0) { + if ($unit == 'now') + $age = 'now'; + elseif ($unit == 'day') + $age = 'today'; + else + $age = 'this ' . $unit; + } + else { + if (isset($relative)) + $age = $relative; + elseif ($approx > $span) + $age = 'almost'; + elseif ($approx < $span) + $age = 'over'; + else + $age = ''; + + $age .= ' ' . self::_n($approx, $unit); + + if ($suffix) { + if ($backwards) + $age .= ' from now'; + else + $age .= ' ago'; + } + } + + $age = ''.__($age, true).''; + + return $age; + } + +/***************************** + ** Test code + **/ +/* function basevals($max) { */ +/* return incrvals($max); */ +/* if ($max % 2) { */ +/* return array(0, 1, ($max+1)/2-1, $max/2, ($max+1)/2, $max-1); */ +/* } */ +/* else { */ +/* return array(0, 1, $max/2-1, $max/2, $max/2+1, $max-1); */ +/* } */ +/* } */ + +/* function incrvals($max, $suppress = false) { */ +/* if ($suppress) */ +/* //return array(0); */ +/* return array(0, 4, $max-1); */ + +/* //return array(0, 1, $max/3, (int)(($max+1)/2)-1, 2*$max/3, 3*$max/4, 4*$max/5, $max-1); */ +/* return array(0, 1, 3, $max/2+2, (int)(($max+1)/2)+4, 2*$max/3, 3*$max/4, 4*$max/5, $max-1); */ +/* } */ + +/* echo('' . "\n"); */ +/* foreach (incrvals(10) AS $y) { */ +/* foreach (incrvals(12, $y) AS $w) { */ +/* foreach (incrvals(30, $y||$w) AS $d) { */ +/* foreach (incrvals(24, $y||$w||$d) AS $h) { */ +/* foreach (incrvals(60, $y||$w||$d||$h) AS $m) { */ +/* foreach ($y||$w||$d||$h||$m ? array(0,1,59) : basevals(60) AS $s) { */ +/* $seconds = 60*(60*(24*(30*(12*$y +$w) + $d) + $h) + $m) + $s; */ +/* //$seconds = 60*(60*(24*(7*(52*$y +$w) + $d) + $h) + $m) + $s; */ +/* $age = time() - $seconds; */ +/* echo ('' */ +/* . ' ' */ +/* . ' ' */ +/* . ' ' */ +/* . ' ' */ +/* .'' . "\n"); */ +/* } */ +/* } */ +/* } */ +/* } */ +/* } */ +/* } */ +/* echo('
' . "y:$y; M:$w; d:$d; h:$h; m:$m; s:$s" . '' . FormatHelper::datetime($age) . '' . FormatHelper::age($age) . '' . $seconds . '; days='.($seconds/60/60/24).'
' . "\n"); */ + + + // Helper function to convert PHP vars to javascript + function phpVarToJavascript($var, $name = '', $depth='', $special = false) { + + // Establish a prefix to use before printing $var + $prefix = $depth; + + // If given a name, set it up JS style + if ($name) + $prefix .= $name . ": "; + + if (!isset($var)) + return $prefix . 'null'; + + if (is_bool($var)) + return $prefix . ($var ? "true" : "false"); + + if (is_numeric($var)) + return $prefix . $var; + + if (is_string($var)) { + // Check to see if this is a special + if ($special) + return $prefix . $var; + + // OK, must just be a string after all + $str = ''; + $first = true; + $lines = explode("\n", $var); + //pr(compact('var', 'lines')); + foreach ($lines AS $line) { + if (!$first) + $str .= ' + "\n" +' . "\n"; + $str .= $prefix . "'" . preg_replace("/'/", '\\\'', $line) . "'"; + $first = false; + } + return $str; + } + + // The only thing left that we know how to dump + // is an array. Die if we have anything else. + if (!is_array($var)) { + die; + return null; + } + + // Keep a lookout for special cases, flagged by '--special' + + // eg: array('name' => array('--special' => array('a' => 'one', 'b' => 'two'))) + // GIVES: name: [ 'one', 'two' ] + // NOT: name: { --special: { a: 'one', b: 'two' } } + + // eg: array('name' => array('--special' => 'varname')) + // GIVES: name: varname + // NOT: name: { --special: 'varname' } + + if (isset($var['--special']) && count($var) == 1) + return self::phpVarToJavascript($var['--special'], $name, $depth, true); + + // PHP array indices can be a mix of integer and string based. + // Just guess here, unless flagged as a special case. + if (isset($var[0]) || $special) + return ($prefix . "[\n" + . implode(",\n", + array_map(array('FormatHelper', 'phpVarToJavascript'), + array_values($var), + array(), + array_fill(0, count($var), $depth.' ') + )) + . "\n$depth]"); + + return ($prefix . "{\n" + . implode(",\n", + array_map(array('FormatHelper', 'phpVarToJavascript'), + array_values($var), array_keys($var), + array_fill(0, count($var), $depth.' ') + )) + . "\n$depth}"); + } + +} + + diff --git a/views/helpers/grid.php b/views/helpers/grid.php new file mode 100644 index 0000000..b9569c1 --- /dev/null +++ b/views/helpers/grid.php @@ -0,0 +1,232 @@ +reset(); + } + + function reset() { + $this->jqGrid_options + = array('limit' => 20, + 'search_fields' => array(), + 'custom_post_data' => array(), + ); + + $this->columns = array(); + $this->included = array(); + $this->invalid = array(); + $this->controller = null; + + return $this; + } + + // Add to the set of columns for this grid + function columns($columns) { + if (isset($columns)) { + if (is_array($columns)) + $this->columns = array_merge($this->columns, $columns); + else + $this->columns[] = $columns; + } + return $this; + } + + // Default fields will be included unless excluded. + // This should be used for fields that ALWAYS need + // to be included unless specifically overridden. + function defaultFields($fields) { + if (isset($fields)) { + if (is_array($fields)) + $this->included = array_merge($this->included, $fields); + else + $this->included[] = $fields; + } + return $this; + } + + // Invalid fields will simply not be accepted as + // part of the inclusion set, even if specified as + // a column AND explicit requested for inclusion. + function invalidFields($fields) { + if (isset($fields)) { + if (is_array($fields)) + $this->invalid = array_merge($this->invalid, $fields); + else + $this->invalid[] = $fields; + } + return $this; + } + + function searchFields($fields) { + if (isset($fields)) { + if (is_array($fields)) + $this->jqGrid_options['search_fields'] + = array_merge($this->jqGrid_options['search_fields'], $fields); + else + $this->jqGrid_options['search_fields'][] = $fields; + } + return $this; + } + + function sortField($field, $order = null) { + $this->jqGrid_options['sort_column'] = $field; + if ($order) + $this->jqGrid_options['sort_order'] = $order; + return $this; + } + + function limit($limit) { + $this->jqGrid_options['limit'] = $limit; + return $this; + } + + function id_list($items) { + $this->jqGrid_options['custom_ids'] + = array_map(create_function('$data', + 'return $data["id"];'), + $items); + $this->jqGrid_options['action'] = 'idlist'; + return $this; + } + + function customData($data) { + if (isset($data)) { + if (is_array($data)) + $this->jqGrid_options['custom_post_data'] + = array_merge($this->jqGrid_options['custom_post_data'], $data); + else + $this->jqGrid_options['custom_post_data'][] = $data; + } + return $this; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: render + * - Renders the grid. + * Unless manipulated with the included / excluded parameters, + * the grid will contain the columns defined as part of the + * standard set, as set through use of the the included() function. + * + * args + * included: + * Array of column names to include in the grid. + * - OR - + * true: includes all columns. + * - OR - + * false: includes no additional columns beyond + * the standard defined set. + + * excluded: + * Array of column names to exclude from the grid. + * These will be excluded even if explicitly included + * through the included parameter. + * - OR - + * true: excludes all columns other than those + * explicitly included through the included parameter. + * - OR - + * false: does not exclude any included columns. + */ + + function render($view, $config = null, $included = true, $excluded = false) { + // Handle null values for included/excluded + if (!isset($included)) + $included = false; + if (!isset($excluded)) + $included = false; + + // Convert true/false into an array + if (is_bool($included) && $included) + $included = array_keys($this->columns); + elseif (is_bool($included) && !$included) + $included = array(); + + // Convert true/false into an array + if (is_bool($excluded) && $excluded) + $excluded = array_diff_key(array_keys($this->columns), $included); + elseif (is_bool($excluded) && !$excluded) + $excluded = array(); + + // Tack on any config include/exclude requests + if (isset($config['include'])) + $included = array_merge($included, $config['include']); + if (isset($config['exclude'])) + $excluded = array_merge($excluded, $config['exclude']); + unset($config['include'], $config['exclude']); + + // Calculate the actual inclusion set + $included = array_diff(array_merge($this->included, $included), + array_merge($this->invalid, $excluded)); + + // Defined the columns, based on the inclusion set, + // remapping column names as necessary. + $this->jqGrid_options['jqGridColumns'] = array(); + foreach (array_intersect_key($this->columns, array_flip($included)) AS $name => $col) { + if (!empty($config['remap'][$name])) + $name = $config['remap'][$name]; + $this->jqGrid_options['jqGridColumns'][$name] = $col; + } + + // Make sure search fields are all part of the inclusion set + $this->jqGrid_options['search_fields'] + = array_intersect($this->jqGrid_options['search_fields'], $included); + + // As an exception to the normal config variables, + // handle 'rows' here. The reason is so that we + // ease the burden on views which have a list of + // items in a CakePHP style array, but jqGrid is + // expecting just an array if IDs. So, we'll run + // as middle man (that is our purpose, after all) + if (isset($config['rows'])) { + // Shrink the limit... user can always override + $this->id_list($config['rows'])->limit(10); + unset($config['rows']); + } + + // One more exception, as the search fields get + // defined, but not passed to jqGrid unless + // specifically requested. + if (isset($config['search'])) + unset($config['search']); + else + unset($this->jqGrid_options['search_fields']); + + // Figure out what controller we're using to + // populate the grid via ajax, and set it. + $controller = $this->controller; + if (!isset($controller)) { + // Try to get the controller based on the convention + // that our grid elements are named after the controller. + // If this doesn't work, user will have to manually set + // $this->controller to whatever they need. + $trace = debug_backtrace(false); + if (preg_match('%elements[/\\\\]([^.]+)\.ctp$%', + $trace[0]['file'], $matches)) + $controller = $matches[1]; + } + $this->jqGrid_options['controller'] = $controller; + + // Incorporate all other user options + if (isset($config)) + $this->jqGrid_options = array_merge($this->jqGrid_options, $config); + + //pr(compact('config') + array('jqGrid_options' => $this->jqGrid_options)); + echo $view->element('jqGrid', $this->jqGrid_options); + + // Since we only have one instance of this class + // as a helper, we must assume it could be used + // again later to render an entirely different grid. + // Reset the variables to prevent contamination. + $this->reset(); + return $this; + } + +} \ No newline at end of file diff --git a/views/layouts/ajax.ctp b/views/layouts/ajax.ctp new file mode 100644 index 0000000..4857f18 --- /dev/null +++ b/views/layouts/ajax.ctp @@ -0,0 +1,25 @@ + + \ No newline at end of file diff --git a/views/layouts/default.ctp b/views/layouts/default.ctp new file mode 100644 index 0000000..5cb289a --- /dev/null +++ b/views/layouts/default.ctp @@ -0,0 +1,93 @@ + + + + + charset(); ?> + + Property Manager: <?php echo $title_for_layout; ?> + + meta('icon') . "\n"; + echo $html->css('cake.generic') . "\n"; + echo $html->css('layout') . "\n"; + echo $html->css('print', null, array('media' => 'print')) . "\n"; + echo $html->css('sidemenu') . "\n"; + echo $javascript->link('http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js') . "\n"; + echo $javascript->link('http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js') . "\n"; + //echo $html->css('themes/base/ui.all') . "\n"; + //echo $html->css('themes/smoothness/ui.all') . "\n"; + //echo $html->css('themes/dotluv/ui.all') . "\n"; + echo $html->css('themes/start/ui.all') . "\n"; + echo $javascript->link('jquery.form') . "\n"; + echo $javascript->link('pmgr') . "\n"; + echo $scripts_for_layout . "\n"; + ?> + + + + + + + + + + + + + + + + +
+ + + element('sidemenu', array('menu' => $sidemenu)); ?> + + + + + +
+
+ + flash(); ?> + + +
+ +
+ + +
+ +
+ + + + + + diff --git a/views/layouts/email/html/default.ctp b/views/layouts/email/html/default.ctp new file mode 100644 index 0000000..72c5442 --- /dev/null +++ b/views/layouts/email/html/default.ctp @@ -0,0 +1,37 @@ + + + + + + <?php echo $title_for_layout;?> + + + + + +

This email was sent using the CakePHP Framework

+ + diff --git a/views/layouts/email/text/default.ctp b/views/layouts/email/text/default.ctp new file mode 100644 index 0000000..4c87cf7 --- /dev/null +++ b/views/layouts/email/text/default.ctp @@ -0,0 +1,29 @@ + + + + +This email was sent using the CakePHP Framework, http://cakephp.org. + diff --git a/views/layouts/flash.ctp b/views/layouts/flash.ctp new file mode 100644 index 0000000..a1b56d7 --- /dev/null +++ b/views/layouts/flash.ctp @@ -0,0 +1,44 @@ + + + + +charset(); ?> +<?php echo $page_title; ?> + + + + + + + + +

+ + \ No newline at end of file diff --git a/views/layouts/js/default.ctp b/views/layouts/js/default.ctp new file mode 100644 index 0000000..d94dc90 --- /dev/null +++ b/views/layouts/js/default.ctp @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/views/layouts/rss/default.ctp b/views/layouts/rss/default.ctp new file mode 100644 index 0000000..94067f2 --- /dev/null +++ b/views/layouts/rss/default.ctp @@ -0,0 +1,17 @@ +header(); + +if (!isset($channel)) { + $channel = array(); +} +if (!isset($channel['title'])) { + $channel['title'] = $title_for_layout; +} + +echo $rss->document( + $rss->channel( + array(), $channel, $content_for_layout + ) +); + +?> \ No newline at end of file diff --git a/views/layouts/xml/default.ctp b/views/layouts/xml/default.ctp new file mode 100644 index 0000000..c688702 --- /dev/null +++ b/views/layouts/xml/default.ctp @@ -0,0 +1,2 @@ +header()); ?> + \ No newline at end of file diff --git a/views/leases/invoice.ctp b/views/leases/invoice.ctp new file mode 100644 index 0000000..8f5c5f6 --- /dev/null +++ b/views/leases/invoice.ctp @@ -0,0 +1,429 @@ + + +
+ + + + +element('leases', array + ('config' => array + ('grid_div_id' => 'leases-list', + 'grid_div_class' => 'text-below', + 'caption' => ('Select Lease'), + 'grid_setup' => array('hiddengrid' => isset($lease['id'])), + 'grid_events' => array('onSelectRow' => + array('ids' => + 'if (ids != null){onRowSelect("#"+$(this).attr("id"), ids);}'), + 'onHeaderClick' => + array('gridstate' => + 'onGridState("#"+$(this).attr("id"), gridstate)'), + ), + 'action' => 'active', + 'nolinks' => true, + 'limit' => 10, + ))); + +echo ('
' . + + '' . + + '' . + + '
' . "\n"); + +echo $form->create(null, array('id' => 'invoice-form', + 'url' => array('controller' => 'transactions', + 'action' => 'postInvoice'))); + +echo $form->input("Lease.id", + array('id' => 'lease-id', + 'type' => 'hidden', + 'value' => 0)); + +/* echo '
' . "\n"; */ +/* echo ' Invoice' . "\n"; */ + +echo $this->element('form_table', + array('class' => "item invoice transaction entry", + //'with_name_after' => ':', + 'field_prefix' => 'Transaction', + 'fields' => array + ("stamp" => array('opts' => + array('type' => 'text'), + 'between' => 'Now', + ), + "comment" => array('opts' => array('size' => 50), + ), + ))); + +/* echo '
' . "\n"; */ + +if (empty($movein)) { + echo "
\n"; + echo $form->input('repeat', array('type' => 'checkbox', + 'id' => 'repeat', + 'label' => 'Enter Multiple Invoices')) . "\n"; +} + +echo $form->submit('Generate Invoice') . "\n"; +?> + +
+ Charges + +
+
+ Add Another Charge +
+
+ +end('Generate Invoice'); ?> + +
+ + + + + + +
diff --git a/views/leases/move.ctp b/views/leases/move.ctp new file mode 100644 index 0000000..028a8d6 --- /dev/null +++ b/views/leases/move.ctp @@ -0,0 +1,249 @@ +action; +//$move_class = preg_replace("/_/", "-", $this->action); +//$move_date = preg_replace("/_/", "", $this->action) . "_date"; +$move_type = preg_replace("/.*_/", "", $this->action); + +?> + +
+ + + + + +element('customers', array + ('config' => array + ('grid_div_id' => 'customers-list', + 'grid_div_class' => 'text-below', + 'caption' => ('Select Customer'), + 'grid_setup' => array('hiddengrid' => isset($customer['id'])), + 'grid_events' => array('onSelectRow' => + array('ids' => + 'if (ids != null){onRowSelect("#"+$(this).attr("id"), "customer", ids);}'), + 'onHeaderClick' => + array('gridstate' => + 'onGridState("#"+$(this).attr("id"), "customer", gridstate)'), + ), + 'nolinks' => true, + 'limit' => 10, + ))); +} + +echo ('
' . + + '' . + + '' . + + '
' . "\n"); + + +if ($move_type !== 'out') { + echo $this->element('units', array + ('config' => array + ('grid_div_id' => 'units-list', + 'grid_div_class' => 'text-below', + 'caption' => ('Select Unit'), + 'grid_setup' => array('hiddengrid' => isset($unit['id'])), + 'grid_events' => array('onSelectRow' => + array('ids' => + 'if (ids != null){onRowSelect("#"+$(this).attr("id"), "unit", ids);}'), + 'onHeaderClick' => + array('gridstate' => + 'onGridState("#"+$(this).attr("id"), "unit", gridstate)'), + ), + 'include' => array('Deposit'), + 'exclude' => array('Balance'), + 'action' => 'vacant', + 'nolinks' => true, + 'limit' => 10, + ))); +} + +echo ('
' . + + '' . + + '' . + + '
' . "\n"); + +echo $form->create(null, array('id' => 'move-inout-form', + 'url' => array('controller' => 'leases', + 'action' => $move_action))); + +echo $form->input("Lease.customer_id", + array('id' => 'customer-id', + 'type' => 'hidden', + 'value' => 0)); + +echo $form->input("Lease.unit_id", + array('id' => 'unit-id', + 'type' => 'hidden', + 'value' => 0)); + +if ($move_type === 'out') { + echo $form->input('Lease.id', + array('type' => 'hidden', + 'value' => $lease['id'], + )); +} + +echo $this->element('form_table', + array('class' => "item move-inout entry", + 'field_prefix' => 'Lease', + 'fields' => array + ('move'.$move_type.'_date' => + array('opts' => + array('type' => 'text', + 'id' => "LeaseMoveDate"), + 'between' => 'Now', + ), + ) + + ($move_type === 'in' ? array + ("deposit" => + array('opts' => array + ('value' => (!empty($unit) + ? FormatHelper::currency($unit['deposit']) + : null))), + "rent" => + array('opts' => array + ('value' => (!empty($unit) + ? FormatHelper::currency($unit['rent']) + : null))), + ) : array()) + array + ("comment" => + ($move_type !== 'out' + ? array('opts' => array('size' => 50)) + : null), + ))); + +if ($move_type === 'out') { + echo('


Be sure that you really want to move this customer out of the unit,
' . + 'select the correct moveout date,' . + 'and press the "Perform Move Out" button.' . "\n"); +} + +// Set up a redirect page. I use lower case 'redirect' here +// to avoid the model convention, which starts with upper-case. +if (isset($redirect)) { + foreach ($redirect AS $name => $value) { + echo $form->input("redirect.$name", + array('type' => 'hidden', + 'value' => $value, + )); + } +} + +echo $form->end('Perform Move ' . ucfirst($move_type)); + +?> + + + +

diff --git a/views/leases/view.ctp b/views/leases/view.ctp new file mode 100644 index 0000000..b68767c --- /dev/null +++ b/views/leases/view.ctp @@ -0,0 +1,121 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Lease Detail Main Section + */ + +$lease_type = $lease['LeaseType']; +$customer = $lease['Customer']; +$unit = $lease['Unit']; + +if (isset($lease['Lease'])) + $lease = $lease['Lease']; + +$rows = array(); + +$rows[] = array('ID', $lease['id']); +$rows[] = array('Number', $lease['number']); +$rows[] = array('Lease Type', $lease_type['name']); +$rows[] = array('Unit', $html->link($unit['name'], + array('controller' => 'units', + 'action' => 'view', + $unit['id']))); +$rows[] = array('Customer', $html->link($customer['name'], + array('controller' => 'customers', + 'action' => 'view', + $customer['id']))); +$rows[] = array('Lease_Date', FormatHelper::date($lease['lease_date'], true)); +$rows[] = array('Move-in Planned', FormatHelper::date($lease['movein_planned_date'], true)); +$rows[] = array('Move-in', FormatHelper::date($lease['movein_date'], true)); +$rows[] = array('Move-out', FormatHelper::date($lease['moveout_date'], true)); +$rows[] = array('Move-out Planned', FormatHelper::date($lease['moveout_planned_date'], true)); +$rows[] = array('Notice Given', FormatHelper::date($lease['notice_given_date'], true)); +$rows[] = array('Notice Received', FormatHelper::date($lease['notice_received_date'], true)); +$rows[] = array('Closed', FormatHelper::date($lease['close_date'], true)); +$rows[] = array('Deposit', FormatHelper::currency($lease['deposit'])); +$rows[] = array('Rent', FormatHelper::currency($lease['rent'])); +$rows[] = array('Comment', $lease['comment']); + + +echo $this->element('table', + array('class' => 'item lease detail', + 'caption' => 'Lease Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Lease Info Box + */ + +echo '
' . "\n"; +$rows = array(); +$rows[] = array('Security Deposit:', FormatHelper::currency($outstandingDeposit)); +$rows[] = array('Balance Owed:', FormatHelper::currency($outstandingBalance)); +$rows[] = array('Paid Through:', FormatHelper::date($lease['paid_through_date'], false)); +if ($lease['delinquent']) + $rows[] = array('Delinquent:', FormatHelper::age($lease['paid_through_date'], 'delinquent')); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Lease Account History + */ + +echo $this->element('statement_entries', array + (// Grid configuration + 'config' => array + ('caption' => 'Lease Statement', + 'filter' => array('Lease.id' => $lease['id']), + 'include' => array('Through'), + 'exclude' => array('Customer', 'Lease', 'Unit'), + 'sort_column' => 'Effective', + 'sort_order' => 'DESC', + ))); + + +/********************************************************************** + * Receipt History + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ('caption' => 'Customer Receipts', + 'filter' => array('Customer.id' => $customer['id'], + 'Transaction.type' => 'RECEIPT', + 'Tender.id !=' => null, + //'Account.id !=' => '-AR-' + ), + 'exclude' => array('Account', 'Cr/Dr'), + 'sort_column' => 'Date', + 'sort_order' => 'DESC', + ))); + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/ledger_entries/view.ctp b/views/ledger_entries/view.ctp new file mode 100644 index 0000000..5e23907 --- /dev/null +++ b/views/ledger_entries/view.ctp @@ -0,0 +1,83 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Ledger Entry Detail Main Section + */ + +$transaction = $entry['Transaction']; +$ledger = $entry['Ledger']; +$account = $ledger['Account']; +$tender = $entry['Tender']; +$matching = $entry['MatchingEntry']; +$double = $entry['DoubleEntry']; +$entry = $entry['LedgerEntry']; + +$rows = array(); +$rows[] = array('ID', $entry['id']); +$rows[] = array('Transaction', $html->link('#'.$transaction['id'], + array('controller' => 'transactions', + 'action' => 'view', + $transaction['id']))); +$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp'])); +$rows[] = array('Amount', FormatHelper::currency($entry['amount'])); +$rows[] = array('Tender', $html->link($tender['name'], + array('controller' => 'tenders', + 'action' => 'view', + $tender['id']))); +$rows[] = array('Account', $html->link($account['name'], + array('controller' => 'accounts', + 'action' => 'view', + $account['id']))); +$rows[] = array('Ledger', $html->link($ledger['name'], + array('controller' => 'ledgers', + 'action' => 'view', + $ledger['id']))); +$rows[] = array('Cr/Dr', ($entry['crdr'] . + ' (Matching ' . $matching['crdr'] . ': ' . + $html->link('#'.$matching['id'], + array('controller' => 'ledger_entries', + 'action' => 'view', + $matching['id'])) . + ')')); +$rows[] = array('Double Entry', $html->link('#'.$double['id'], + array('controller' => 'double_entries', + 'action' => 'view', + $double['id']))); +$rows[] = array('Comment', $entry['comment']); + +echo $this->element('table', + array('class' => 'item entry detail', + 'caption' => 'Ledger Entry Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Entry Info Box + */ + +echo '
' . "\n"; + +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/ledgers/view.ctp b/views/ledgers/view.ctp new file mode 100644 index 0000000..a8a00f5 --- /dev/null +++ b/views/ledgers/view.ctp @@ -0,0 +1,84 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Ledger Detail Main Section + */ + +$account = $ledger['Account']; +//$close = $ledger['CloseTransaction']; + +if (isset($ledger['Ledger'])) + $ledger = $ledger['Ledger']; + +$rows = array(); +$rows[] = array('ID', $ledger['id']); +$rows[] = array('Name', $ledger['name']); +$rows[] = array('Account', $html->link($account['name'], + array('controller' => 'accounts', + 'action' => 'view', + $account['id']))); +$rows[] = array('Sequence', $ledger['sequence']); +$rows[] = array('Status', $ledger['close_transaction_id'] ? 'Closed' : 'Open'); +$rows[] = array('Comment', $ledger['comment']); + +echo $this->element('table', + array('class' => 'item ledger detail', + 'caption' => 'Ledger Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Ledger Info Box + */ + +echo '
' . "\n"; +$rows = array(); +$rows[] = array('Debits:', FormatHelper::currency($stats['debits'])); +$rows[] = array('Credits:', FormatHelper::currency($stats['credits'])); +$rows[] = array('Ledger Balance:', FormatHelper::currency($stats['balance'])); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Ledger Entries + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ('caption' => "Ledger Entries", + 'filter' => array('Ledger.id' => $ledger['id']), + 'exclude' => array('Ledger', 'Account', + 'Amount', 'Cr/Dr', 'Balance', + empty($account['receipts']) ? 'Tender' : null), + 'include' => array('Debit', 'Credit', 'Sub-Total'), + ))); + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/maps/image.ctp b/views/maps/image.ctp new file mode 100644 index 0000000..f320313 --- /dev/null +++ b/views/maps/image.ctp @@ -0,0 +1,99 @@ + 20 || ($unit['width'] >= $unit['depth'])) { + imagestring($image, $unit['width']/25, + $unit['left']+3, $unit['top']+3, + $unit['name'], + $info['palate']['unit'][$unit['status']]['fg']['color']); + } + else { + imagestringup($image, $unit['depth']/25, + $unit['left']+3, $unit['bottom']-3, + $unit['name'], + $info['palate']['unit'][$unit['status']]['fg']['color']); + } + +} + +if ($debug) { + print("

Would Now Render Image as image/png

\n"); +} +else { + header("Content-type: image/png"); + if (! imagepng($image)) { + die("Couldn't output image!"); + } +} + +// Clear image from memory +imagedestroy($image); + +// Closing PHP tag intentionally omitted diff --git a/views/maps/view.ctp b/views/maps/view.ctp new file mode 100644 index 0000000..dd2663e --- /dev/null +++ b/views/maps/view.ctp @@ -0,0 +1,63 @@ + + +
+ +url(array('controller' => 'units', + 'action' => 'view', + $unit['id'])) . + '" alt="Unit #' . + $unit['name'] . + '" title="Unit #' . + $unit['name'] . + (empty($unit['data']['CurrentLease']['id']) + ? '' + : ('; ' . +/* 'Lease #' . */ +/* $unit['data']['CurrentLease']['id'] . */ +/* '; ' . */ + $unit['data']['Customer']['name'] . + '; Paid Through ' . + $unit['data']['CurrentLease']['paid_through_date']) + ) . + '">' . "\n"); + } + }// for indentation purposes + ?> + + + image(array('controller' => 'maps', + 'action' => 'map', + $info['map_id'], + $info['width']), + array('alt' => 'Site Map', + 'class' => 'map', + 'border' => 0, + 'usemap' => '#mapzones')); + ?> + +
+ +
+image(array('controller' => 'maps', + 'action' => 'legend', + $info['map_id'], + $info['width']/2), + array('alt' => 'Site Map Legend', + 'class' => 'legend')); +?> +
+ + diff --git a/views/statement_entries/reverse.ctp b/views/statement_entries/reverse.ctp new file mode 100644 index 0000000..0fbb3d9 --- /dev/null +++ b/views/statement_entries/reverse.ctp @@ -0,0 +1,74 @@ +' . "\n"; + +$customer = $entry['Customer']; +$transaction = $entry['Transaction']; +$account = $entry['Account']; + +if (isset($entry['StatementEntry'])) + $entry = $entry['StatementEntry']; + +// We're not actually using a grid to select the customer, +// but selection-text makes for reasonable formatting +echo ('
' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('
' . $customer['name'] . '' . '(Customer #' . $customer['id'] . ')' . '
' . $account['name'] . '' . '(StatementEntry #' . $entry['id'] . ')' . '
Amount:' . FormatHelper::currency($entry['amount']) . '
' . + '
' . "\n"); + + +echo $form->create(null, array('id' => 'reverse-form', + 'url' => array('action' => 'reverse'))) . "\n"; + +echo $form->input("StatementEntry.id", + array('type' => 'hidden', + 'value' => $entry['id'])) . "\n"; + +echo $this->element('form_table', + array('class' => "item reverse transaction entry", + //'with_name_after' => ':', + 'field_prefix' => 'Transaction', + 'fields' => array + ("stamp" => array('opts' => array('type' => 'text'), + 'between' => 'Now', + ), + "comment" => array('opts' => array('size' => 50), + ), + ))) . "\n"; + +echo $form->end('Reverse Charge'); +?> + + + + diff --git a/views/statement_entries/view.ctp b/views/statement_entries/view.ctp new file mode 100644 index 0000000..2a325c0 --- /dev/null +++ b/views/statement_entries/view.ctp @@ -0,0 +1,126 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Entry Detail Main Section + */ + +$transaction = $entry['Transaction']; +$account = $entry['Account']; +$customer = $entry['Customer']; +$lease = $entry['Lease']; +$entry = $entry['StatementEntry']; + +$Ttype = ucwords(strtolower(str_replace('_', ' ', $transaction['type']))); + +$rows = array(); +$rows[] = array('ID', $entry['id']); +$rows[] = array($Ttype, $html->link('#'.$transaction['id'], + array('controller' => 'transactions', + 'action' => 'view', + $transaction['id']))); +$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp'])); +$rows[] = array('Effective', FormatHelper::date($entry['effective_date'])); +if (in_array($entry['type'], array('CHARGE', 'PAYMENT'))) + $rows[] = array('Through', FormatHelper::date($entry['through_date'])); +$rows[] = array('Type', $entry['type']); +$rows[] = array('Amount', FormatHelper::currency($entry['amount'])); +$rows[] = array('Account', $html->link($account['name'], + array('controller' => 'accounts', + 'action' => 'view', + $account['id']))); +$rows[] = array('Customer', (isset($customer['name']) + ? $html->link($customer['name'], + array('controller' => 'customers', + 'action' => 'view', + $customer['id'])) + : null)); +$rows[] = array('Lease', (isset($lease['id']) + ? $html->link('#'.$lease['id'], + array('controller' => 'leases', + 'action' => 'view', + $lease['id'])) + : null)); +$rows[] = array('Comment', $entry['comment']); + +echo $this->element('table', + array('class' => 'item statement-entry detail', + 'caption' => 'Statement Entry Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Entry Info Box + */ + +if (strtoupper($entry['type']) === 'CHARGE') { + $applied_caption = "Disbursements Applied"; + //$remaining_caption = "Charge Balance"; +} +else { + $applied_caption = "Disbursed to Charges"; + //$remaining_caption = "Disbursement Balance"; +} + +$remaining_caption = "Remaining Balance"; + +echo '
' . "\n"; + +$rows = array(); +$rows[] = array($applied_caption, + '' . + FormatHelper::currency($stats['reconciled']) . + ''); +$rows[] = array($remaining_caption, + '' . + FormatHelper::currency($stats['balance']) . + ''); + +echo $this->element('table', + array('class' => 'item summary', + 'caption' => null, + 'rows' => $rows, + 'column_class' => array('field', 'value'), + //'suppress_alternate_rows' => true, + )); + +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Reconciliation Ledger Entries + */ + +echo $this->element('statement_entries', array + (// Element configuration + 'statement_entry_id' => $entry['id'], +/* 'action' => 'reconcile', */ + + // Grid configuration + 'config' => array + ('caption' => $applied_caption, + //'filter' => array('id' => $entry['id']), + 'exclude' => array('Transaction'), + ))); + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/tenders/deposit.ctp b/views/tenders/deposit.ctp new file mode 100644 index 0000000..b073b28 --- /dev/null +++ b/views/tenders/deposit.ctp @@ -0,0 +1,167 @@ +' . "\n"; +echo '

Perform Bank Deposit

' . "\n"; +echo '

Make sure to select the checkboxes below for only those types of currency (Cash, Check, etc) which you intend to actually deposit (you can see all the individual items by dropping down the list below the checkbox). Then, select the Deposit Account where you will make the deposit, and click "Perform Deposit" to close the books on the selected currency types and reset them to a zero balance. On the next page, you will be provided with a deposit slip to prepare the actual deposit.' . "\n"; +echo '


' . "\n"; + +//pr(compact('depositTypes', 'depositAccounts')); + +echo $form->create(null, array('id' => 'deposit-form', + 'onsubmit' => 'return verifyRequest();', + 'url' => array('controller' => 'transactions', + 'action' => 'postDeposit'))); + +foreach ($depositTypes AS $type) { + $names = Inflector::pluralize($type['name']); + + $radioOptions = + array('none' => " No {$names} will be deposited", + 'all' => (" Deposit all {$names} (" . + FormatHelper::currency($type['stats']['undeposited']) . + ")"), + 'subset' => " Deposit {$names} from the list below", + ); + + echo "\n"; + echo $form->input("TenderType.{$type['id']}.selection", + array('type' => 'radio', + 'class' => "type-selection-{$type['id']}", + 'separator' => '
', + 'onclick' => "switchSelection({$type['id']})", + 'legend' => false, + // REVISIT : 20080811; Make opt-in, or opt-out? + 'value' => $type['stats']['undeposited'] > 0 ? 'none' : 'none', + 'disabled' => $type['stats']['undeposited'] <= 0, + 'options' => $radioOptions, + )); + + // REVISIT : 20090729 + // Would like to present an option for the user to close the ledger + // associated with the form of tender, or to just leave it open. + // For now, just close it. + echo "\n"; + echo $form->input("TenderType.{$type['id']}.close", + array('type' => 'hidden', + 'value' => true, + )); + + echo "\n"; + echo $form->input("TenderType.{$type['id']}.amount", + array('type' => 'hidden', + 'value' => $type['stats']['undeposited'], + )); + echo "\n"; + echo $form->input("TenderType.{$type['id']}.id", + array('type' => 'hidden', + 'value' => $type['id'], + )); + echo "\n"; + echo $form->input("TenderType.{$type['id']}.name", + array('type' => 'hidden', + 'value' => $type['name'], + )); + echo "\n"; + + $grid_div_id = "tenders-{$type['id']}-list"; + echo $this->element('tenders', array + (// Grid configuration + 'config' => array + ( + 'grid_div_id' => $grid_div_id, + + 'grid_setup' => + array('hiddengrid' => true, + 'multiselect' => true), + + 'caption' => "{$names} on hand", + 'filter' => array('deposit_transaction_id' => null, + 'TenderType.id' => $type['id']), + 'exclude' => array('Type'), + ), + )); + + // Add a hidden item to hold the jqGrid selection, + // which we'll populate prior to form submission. + echo "\n"; + echo $form->input("TenderType.{$type['id']}.items", + array('type' => 'hidden', + 'value' => null + )); + +} + +echo $form->input('Deposit.Account.id', array('label' => 'Deposit Account ', + 'options' => $depositAccounts)); +echo $form->end('Perform Deposit'); + +/* End page div */ +echo '' . "\n"; + +?> + + \ No newline at end of file diff --git a/views/tenders/edit.ctp b/views/tenders/edit.ctp new file mode 100644 index 0000000..600c5d1 --- /dev/null +++ b/views/tenders/edit.ctp @@ -0,0 +1,90 @@ +' . "\n"; + +?> + +create('Tender', array('action' => 'edit')) . "\n"; +echo $form->input('id') . "\n"; + +if (empty($this->data['Tender'])) + INTERNAL_ERROR('Creation of new Tender not allowed.'); + +echo $form->input('tender_type_id', + array('div' => 'tender input', + // REVISIT : 20090810 + // We're not ready to allow changing the type + // of a tender, since it will force us to deal + // with changing the LedgerEntry account (easy) + // and the associated StatementEntry accounts + // (not too hard), and make sure the tender has + // not been deposited (easy), and then deal with + // any corner cases that pop up. + 'disabled' => true, + 'onclick' => ('switchTenderType(' . + '"tender-type-div", ' . + '$(this).attr("id")' . + ')'), + )) . "\n"; + +$form->input('comment'); +foreach ($types AS $type) { + $type = $type['TenderType']; + echo('data['TenderType']['id'] + ? ' STYLE="display:none;"' : '') . + '>' . "\n"); + + echo ('' . "\n"); + + for ($i=1; $i<=4; ++$i) { + if (!empty($type["data{$i}_name"])) { + echo $form->input("type.{$type['id']}.data$i", + array('label' => $type["data{$i}_name"], + 'div' => 'input text tender', + 'value' => + ($type['id'] == $this->data['TenderType']['id'] + ? $this->data['Tender']["data$i"] : null), + )) . "\n"; + +/* echo ('

' . */ +/* ' ' . */ +/* $type["data{$i}_name"] . */ +/* '' . "\n" . */ +/* '
' . "\n"); */ + } + } + + echo('' . "\n"); +} + +echo $form->submit('Update') . "\n"; +echo $form->submit('Cancel', array('name' => 'cancel')) . "\n"; +echo $form->end() . "\n"; +echo '' . "\n"; diff --git a/views/tenders/nsf.ctp b/views/tenders/nsf.ctp new file mode 100644 index 0000000..a5e5af3 --- /dev/null +++ b/views/tenders/nsf.ctp @@ -0,0 +1,74 @@ +' . "\n"; + +$customer = $tender['Customer']; +$entry = $tender['LedgerEntry']; +$transaction = $entry['Transaction']; + +if (isset($tender['Tender'])) + $tender = $tender['Tender']; + +// We're not actually using a grid to select the customer, +// but selection-text makes for reasonable formatting +echo ('
' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('
' . $customer['name'] . '' . '(Customer #' . $customer['id'] . ')' . '
' . $tender['name'] . '' . '(Tender #' . $tender['id'] . ')' . '
Amount:' . FormatHelper::currency($entry['amount']) . '
' . + '
' . "\n"); + + +echo $form->create(null, array('id' => 'nsf-form', + 'url' => array('action' => 'nsf'))) . "\n"; + +echo $form->input("Tender.id", + array('type' => 'hidden', + 'value' => $tender['id'])) . "\n"; + +echo $this->element('form_table', + array('class' => "item receipt transaction entry", + //'with_name_after' => ':', + 'field_prefix' => 'Transaction', + 'fields' => array + ("stamp" => array('opts' => array('type' => 'text'), + 'between' => 'Now', + ), + "comment" => array('opts' => array('size' => 50), + ), + ))) . "\n"; + +echo $form->end('Record Item as NSF'); +?> + + + + diff --git a/views/tenders/view.ctp b/views/tenders/view.ctp new file mode 100644 index 0000000..335fccc --- /dev/null +++ b/views/tenders/view.ctp @@ -0,0 +1,100 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Tender Detail Main Section + */ + +$ttype = $tender['TenderType']; +$customer = $tender['Customer']; +$entry = $tender['LedgerEntry']; +$transaction = $entry['Transaction']; +$tender = $tender['Tender']; + +$rows = array(); +$rows[] = array('ID', $tender['id']); +$rows[] = array('Received', FormatHelper::date($transaction['stamp'])); +$rows[] = array('Customer', $html->link($customer['name'], + array('controller' => 'customers', + 'action' => 'view', + $customer['id']))); +$rows[] = array('Amount', FormatHelper::currency($entry['amount'])); +$rows[] = array('Item', $tender['name']); +$rows[] = array('Type', $ttype['name']); +/* $rows[] = array('Type', $html->link($ttype['name'], */ +/* array('controller' => 'tender_types', */ +/* 'action' => 'view', */ +/* $ttype['id']))); */ + +for ($i=1; $i<=4; ++$i) + if (!empty($ttype["data{$i}_name"])) + $rows[] = array($ttype["data{$i}_name"], $tender["data{$i}"]); + +if (!empty($tender['deposit_transaction_id'])) + $rows[] = array('Deposit', $html->link('#'.$tender['deposit_transaction_id'], + array('controller' => 'transactions', + 'action' => 'view', + $tender['deposit_transaction_id']))); + +if (!empty($tender['nsf_transaction_id'])) + $rows[] = array('NSF', $html->link('#'.$tender['nsf_transaction_id'], + array('controller' => 'transactions', + 'action' => 'view', + $tender['nsf_transaction_id']))); + +$rows[] = array('Comment', $tender['comment']); + +echo $this->element('table', + array('class' => 'item tender detail', + 'caption' => 'Legal Tender Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Tender Info Box + */ + +echo '
' . "\n"; +$rows = array(); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Ledger Entries + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ('caption' => "Ledger Entries", + 'filter' => array('id' => array($tender['ledger_entry_id'], $tender['nsf_ledger_entry_id'])), + 'exclude' => array('Tender'), + ))); + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/transactions/bad_debt.ctp b/views/transactions/bad_debt.ctp new file mode 100644 index 0000000..668f260 --- /dev/null +++ b/views/transactions/bad_debt.ctp @@ -0,0 +1,89 @@ +' . "\n"; + +if (isset($lease)) { + $customer = $lease['Customer']; + $unit = $lease['Unit']; +} + +if (isset($customer['Customer'])) + $customer = $customer['Customer']; + +if (isset($lease['Lease'])) + $lease = $lease['Lease']; + +// We're not actually using a grid to select the customer / lease +// but we could/should be, and the result would be selection-text +echo ('
' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +if (isset($lease)) + echo ('' . + ' ' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('
' . $customer['name'] . '' . '(Customer #' . $customer['id'] . ')' . '
' . 'Unit ' . $unit['name'] . '' . '(Lease #' . $lease['number'] . ')' . '
Remaining Balance:' . FormatHelper::currency($balance) . '
' . + '
' . "\n"); + + +echo $form->create(null, array('id' => 'receipt-form', + 'url' => array('controller' => 'transactions', + 'action' => 'postWriteOff'))) . "\n"; + +echo $form->input("Customer.id", + array('type' => 'hidden', + 'value' => $customer['id'])) . "\n"; + +if (isset($lease['id'])) + echo $form->input("Lease.id", + array('type' => 'hidden', + 'value' => $lease['id'])) . "\n"; + +echo $form->input("Entry.0.amount", + array('type' => 'hidden', + 'value' => $balance)) . "\n"; + +echo $this->element('form_table', + array('class' => "item receipt transaction entry", + //'with_name_after' => ':', + 'field_prefix' => 'Transaction', + 'fields' => array + ("stamp" => array('opts' => array('type' => 'text'), + 'between' => 'Now', + ), + "comment" => array('opts' => array('size' => 50), + ), + ))) . "\n"; + +echo $form->end('Write Off Remaining Balance'); +?> + + + + diff --git a/views/transactions/deposit_slip.ctp b/views/transactions/deposit_slip.ctp new file mode 100644 index 0000000..99a25c8 --- /dev/null +++ b/views/transactions/deposit_slip.ctp @@ -0,0 +1,67 @@ +Deposit Slip: ' . + FormatHelper::datetime($deposit['Transaction']['stamp']) + . '' . "\n"); +/* echo('(' . */ +/* FormatHelper::age($deposit['Transaction']['stamp'], 60) */ +/* . ')
' . "\n"); */ + +//pr(compact('deposit')); + +// Handle account summaries +$rows = array(); +$row_class = array(); +foreach ($deposit['types'] AS $type) { + $row_class[] = array(); + $rows[] = array($type['name'].':', + FormatHelper::_n($type['count'], 'Item'), + FormatHelper::currency($type['total'], true)); +} +$row_class[] = 'grand'; +$rows[] = array('Deposit Total:', + null, + FormatHelper::currency($deposit['Transaction']['amount'], true)); + +echo $this->element('table', + array('class' => 'deposit-summary', + 'rows' => $rows, + 'row_class' => $row_class, + 'column_class' => array('account', 'quantity', 'total'), + 'suppress_alternate_rows' => true, + )); + + +// Print out the items of each ledger +if (0) { + foreach ($deposit['types'] AS $type) { + echo $this->element('tenders', array + (// Grid configuration + 'config' => array + ( + 'grid_div_id' => "tenders-{$type['id']}-list", + 'caption' => $type['name'] . ' Items', + 'filter' => array('deposit_transaction_id' + => $deposit['Transaction']['id'], + 'TenderType.id' + => $type['id'], + ), + 'exclude' => array('Type'), + ))); + } +} +else { + echo $this->element('tenders', array + (// Grid configuration + 'config' => array + ( + 'caption' => 'Deposited Items', + 'filter' => array('deposit_transaction_id' + => $deposit['Transaction']['id'], + ), + ))); +} + +/* End page div */ +//echo '' . "\n"; diff --git a/views/transactions/refund.ctp b/views/transactions/refund.ctp new file mode 100644 index 0000000..569a07e --- /dev/null +++ b/views/transactions/refund.ctp @@ -0,0 +1,106 @@ +' . "\n"; +echo '

Issue Refund

' . "\n"; +echo '

Enter the amount to refund, and the account to pay it from.' . "\n"; +echo '


' . "\n"; + +if (isset($lease)) { + $customer = $lease['Customer']; + $unit = $lease['Unit']; +} + +if (isset($customer['Customer'])) + $customer = $customer['Customer']; + +if (isset($lease['Lease'])) + $lease = $lease['Lease']; + +// We're not actually using a grid to select the customer / lease +// but we could/should be, and the result would be selection-text +echo ('

' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +if (isset($lease)) + echo ('' . + ' ' . + '' . "\n"); + +echo ('' . + ' ' . + '' . "\n"); + +echo ('
' . $customer['name'] . '' . '(Customer #' . $customer['id'] . ')' . '
' . 'Unit ' . $unit['name'] . '' . '(Lease #' . $lease['number'] . ')' . '
Refundable Balance:' . FormatHelper::currency($balance) . '
' . + '
' . "\n"); + + +echo $form->create(null, array('id' => 'refund-form', + 'url' => array('controller' => 'transactions', + 'action' => 'postRefund'))); + + +// REVISIT : 20090805 +// Add Tender information to log specifically _how_ refund was paid. + +echo $this->element('form_table', + array('class' => "item refund transaction entry", + //'with_name_after' => ':', + 'field_prefix' => 'Transaction', + 'fields' => array + ("stamp" => array('opts' => + array('type' => 'text'), + 'between' => 'Now', + ), + "amount" => array('prefix' => 'Entry.0', + 'opts' => + array('value' => + FormatHelper::currency($balance), + ), + ), + "account_id" => array('prefix' => 'Entry.0', + 'name' => 'Account', + 'opts' => + array('options' => $refundAccounts, + 'value' => $defaultAccount, + ), + ), + "comment" => array('opts' => array('size' => 50), + ), + ))) . "\n"; + +echo $form->input("Customer.id", + array('type' => 'hidden', + 'value' => $customer['id'])) . "\n"; + +if (isset($lease['id'])) + echo $form->input("Lease.id", + array('type' => 'hidden', + 'value' => $lease['id'])) . "\n"; + +echo $form->end('Issue Refund'); +?> + + + + diff --git a/views/transactions/view.ctp b/views/transactions/view.ctp new file mode 100644 index 0000000..e6f1835 --- /dev/null +++ b/views/transactions/view.ctp @@ -0,0 +1,129 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Transaction Detail Main Section + */ + +$account = $transaction['Account']; +$ledger = $transaction['Ledger']; +$nsf_tender = $transaction['NsfTender']; + +if (isset($transaction['Transaction'])) + $transaction = $transaction['Transaction']; + +$rows = array(); +$rows[] = array('ID', $transaction['id']); +$rows[] = array('Type', str_replace('_', ' ', $transaction['type'])); +$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp'])); +$rows[] = array('Amount', FormatHelper::currency($transaction['amount'])); +$rows[] = array('Account', $html->link($account['name'], + array('controller' => 'accounts', + 'action' => 'view', + $account['id']))); +$rows[] = array('Ledger', $html->link($ledger['name'], + array('controller' => 'ledgers', + 'action' => 'view', + $ledger['id']))); +if (!empty($nsf_tender['id'])) + $rows[] = array('NSF Tender', $html->link($nsf_tender['name'], + array('controller' => 'tenders', + 'action' => 'view', + $nsf_tender['id']))); +$rows[] = array('Comment', $transaction['comment']); + +echo $this->element('table', + array('class' => 'item transaction detail', + 'caption' => 'Transaction Detail', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Transaction Info Box + */ + +echo '
' . "\n"; +$rows = array(); +$rows[] = array('Total:', FormatHelper::currency($transaction['amount'])); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Statement Entries + */ + +if ($transaction['type'] === 'INVOICE' || + $transaction['type'] === 'RECEIPT' || + $transaction['type'] === 'CREDIT_NOTE' || + $transaction['type'] === 'PAYMENT' + ) { + echo $this->element('statement_entries', array + (// Grid configuration + 'config' => array + ( + 'caption' => 'Statement Entries', + 'filter' => array('Transaction.id' => $transaction['id'], + 'type !=' => 'VOID'), + 'exclude' => array('Transaction', 'Debit', 'Credit'), + 'include' => array('Amount'), + ))); +} + + +/********************************************************************** + * Ledger Entries + */ + +echo $this->element('ledger_entries', array + (// Grid configuration + 'config' => array + ( + 'caption' => 'Ledger Entries', + 'filter' => array('Transaction.id' => $transaction['id'], + 'Account.id !=' => $account['id']), + 'exclude' => array('Transaction'), + ))); + + +/* /\********************************************************************** */ +/* * Tenders Deposited */ +/* *\/ */ + +/* if ($transaction['type'] === 'DEPOSIT') { */ +/* echo $this->element('tenders', array */ +/* (// Grid configuration */ +/* 'config' => array */ +/* ( */ +/* 'caption' => 'Deposited Items', */ +/* 'filter' => array('deposit_transaction_id' => $transaction['id']), */ +/* ))); */ +/* } */ + + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/views/units/edit.ctp b/views/units/edit.ctp new file mode 100644 index 0000000..22e389b --- /dev/null +++ b/views/units/edit.ctp @@ -0,0 +1,27 @@ +' . "\n"; + +echo $form->create('Unit', array('action' => 'edit')) . "\n"; +echo $form->input('id') . "\n"; + +echo($this->element + ('form_table', + array('class' => 'item unit detail', + 'caption' => isset($this->data['Unit']) ? 'Edit Unit' : 'New Unit', + 'fields' => array + ('name' => true, + 'unit_size_id' => true, + 'status' => array('opts' => + array('options' => $statusEnums, + ), + ), + 'deposit' => true, + 'rent' => true, + 'comment' => true, + ))) . "\n"); + +echo $form->submit('Update') . "\n"; +echo $form->submit('Cancel', array('name' => 'cancel')) . "\n"; +echo $form->end() . "\n"; +echo '' . "\n"; diff --git a/views/units/view.ctp b/views/units/view.ctp new file mode 100644 index 0000000..7c644a0 --- /dev/null +++ b/views/units/view.ctp @@ -0,0 +1,100 @@ +' . "\n"; + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Unit Detail Main Section + */ + +$leases = $unit['Lease']; +$current_lease = $unit['CurrentLease']; +$unit_size = $unit['UnitSize']; + +if (isset($unit['Unit'])) + $unit = $unit['Unit']; + +$rows = array(); +$rows[] = array('Name', $unit['name']); +$rows[] = array('Status', $unit['status']); +$rows[] = array('Size', $unit_size['name']); +$rows[] = array('Deposit', FormatHelper::currency($unit['deposit'])); +$rows[] = array('Rent', FormatHelper::currency($unit['rent'])); +$rows[] = array('Comment', $unit['comment']); + +echo $this->element('table', + array('class' => 'item unit detail', + 'caption' => 'Unit Info', + 'rows' => $rows, + 'column_class' => array('field', 'value'))); + + +/********************************************************************** + * Unit Info Box + */ + +echo '
' . "\n"; +$rows = array(); +$rows[] = array('Security Deposit:', FormatHelper::currency($outstandingDeposit)); +$rows[] = array('Balance:', FormatHelper::currency($outstandingBalance)); +echo $this->element('table', + array('class' => 'summary', + 'rows' => $rows, + 'column_class' => array('field', 'value'), + 'suppress_alternate_rows' => true, + )); +echo '
' . "\n"; + + +/********************************************************************** + ********************************************************************** + ********************************************************************** + ********************************************************************** + * Supporting Elements Section + */ + +echo '
' . "\n"; + + +/********************************************************************** + * Lease History + */ + +echo $this->element('leases', array + (// Grid configuration + 'config' => array + ('caption' => 'Lease History', + 'filter' => array('Unit.id' => $unit['id']), + 'exclude' => array('Unit'), + ))); + + +/********************************************************************** + * Current Customer Lease Account History + */ + +if (isset($current_lease['id'])) { + echo $this->element('statement_entries', array + (// Grid configuration + 'config' => array + ( + 'caption' => + ('Current Lease Statement (' + . $current_lease['Customer']['name'] + . ')'), + 'filter' => array('Lease.id' => $current_lease['id']), + 'include' => array('Through'), + 'exclude' => array('Customer', 'Lease', 'Unit'), + 'sort_column' => 'Effective', + 'sort_order' => 'DESC', + ))); +} + + +/* End "detail supporting" div */ +echo '
' . "\n"; + +/* End page div */ +echo '' . "\n"; diff --git a/webroot/.htaccess b/webroot/.htaccess new file mode 100644 index 0000000..f9d8b93 --- /dev/null +++ b/webroot/.htaccess @@ -0,0 +1,6 @@ + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] + \ No newline at end of file diff --git a/webroot/css.php b/webroot/css.php new file mode 100644 index 0000000..eb9be9f --- /dev/null +++ b/webroot/css.php @@ -0,0 +1,102 @@ +compress($data); + $ratio = 100 - (round(strlen($output) / strlen($data), 3) * 100); + $output = " /* file: $name, ratio: $ratio% */ " . $output; + return $output; + } +/** + * Enter description here... + * + * @param unknown_type $path + * @param unknown_type $content + * @return unknown + */ + function write_css_cache($path, $content) { + if (!is_dir(dirname($path))) { + mkdir(dirname($path)); + } + $cache = new File($path); + return $cache->write($content); + } + + if (preg_match('|\.\.|', $url) || !preg_match('|^ccss/(.+)$|i', $url, $regs)) { + die('Wrong file name.'); + } + + $filename = 'css/' . $regs[1]; + $filepath = CSS . $regs[1]; + $cachepath = CACHE . 'css' . DS . str_replace(array('/','\\'), '-', $regs[1]); + + if (!file_exists($filepath)) { + die('Wrong file name.'); + } + + if (file_exists($cachepath)) { + $templateModified = filemtime($filepath); + $cacheModified = filemtime($cachepath); + + if ($templateModified > $cacheModified) { + $output = make_clean_css($filepath, $filename); + write_css_cache($cachepath, $output); + } else { + $output = file_get_contents($cachepath); + } + } else { + $output = make_clean_css($filepath, $filename); + write_css_cache($cachepath, $output); + $templateModified = time(); + } + + header("Date: " . date("D, j M Y G:i:s ", $templateModified) . 'GMT'); + header("Content-Type: text/css"); + header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT"); + header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1 + header("Pragma: cache"); // HTTP/1.0 + print $output; +?> \ No newline at end of file diff --git a/webroot/css/cake.generic.css b/webroot/css/cake.generic.css new file mode 100644 index 0000000..ff97edb --- /dev/null +++ b/webroot/css/cake.generic.css @@ -0,0 +1,319 @@ +/* SVN FILE: $Id: cake.generic.css 7945 2008-12-19 02:16:01Z gwoo $ */ +/** + * + * PHP versions 4 and 5 + * + * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org) + * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project + * @package cake + * @subpackage cake.app.webroot.css + * @since CakePHP(tm) + * @version $Revision: 7945 $ + * @modifiedby $LastChangedBy: gwoo $ + * @lastmodified $Date: 2008-12-18 18:16:01 -0800 (Thu, 18 Dec 2008) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + + +* { + margin:0; + padding:0; +} + +/* Layout */ +#container { + text-align: left; +} + +#header{ + padding: 10px 20px; +} +#header h1 { + background: #003d4c url('../img/cake.icon.gif') no-repeat left; + color: #fff; + padding: 0px 30px; +} +#header h1 a { + color: #fff; + background: #003d4c; + font-weight: normal; + text-decoration: none; +} +#header h1 a:hover { + color: #fff; + background: #003d4c; + text-decoration: underline; +} +#content{ + background: #fff; + clear: both; + color: #333; +/* padding: 10px 20px 40px 20px; */ + overflow: auto; +} +#footer { + clear: both; + padding: 6px 10px; + text-align: right; +} + + +table.cake-sql-log { + background: #f4f4f4; + empty-cells: show; + border:1px solid #ccc; + border-right:0; + clear: both; + color: #333; + margin-bottom: 10px; + width: 100%; +} +.cake-sql-log td { + background: #fff; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + vertical-align: top; + padding: 4px 8px; + text-align: left; +} +.cake-sql-log th { + background: #f2f2f2; + border:1px solid #bbb; + border-top: 1px solid #fff; + border-left: 1px solid #fff; + text-align: center; +} + + +/* Paging */ +div.paging { + background:#fff; + color: #ccc; + margin-bottom: 2em; +} +div.paging div.disabled { + color: #ddd; + display: inline; +} +div.paging span { +} +div.paging span.current { + color: #000; +} +div.paging span a { +} + +/* Scaffold View */ +dl { + line-height: 2em; + margin: 0em 0em; + width: 60%; +} +dl.altrow { + background: #f4f4f4; +} +dt { + font-weight: bold; + padding-left: 4px; + vertical-align: top; +} +dd { + margin-left: 10em; + margin-top: -2em; + vertical-align: top; +} + +/* Notices and Errors */ +div.message { + clear: both; + color: #900; + font-size: 140%; + font-weight: bold; + margin: 1em 0; +} +div.error-message { + clear: both; + color: #900; + font-weight: bold; +} +p.error { + background-color: #e32; + color: #fff; + font-family: Courier, monospace; + font-size: 120%; + line-height: 140%; + padding: 0.8em; + margin: 1em 0; +} +p.error em { + color: #000; + font-weight: normal; + line-height: 140%; +} +.notice { + background: #ffcc00; + color: #000; + display: block; + font-family: Courier, monospace; + font-size: 120%; + line-height: 140%; + padding: 0.8em; + margin: 1em 0; +} +.success { + background: green; + color: #fff; +} + +/* Actions */ +td.actions { + text-align: center; + white-space: nowrap; +} +td.actions a { + margin: 0px 6px; +} + +div.actions ul { + margin: 0px 0; + padding: 0; +} +div.actions li { + display: inline; + list-style-type: none; + line-height: 2em; + margin: 0 2em 0 0; + white-space: nowrap; +} +div.actions ul li a { + background:#fff; + color: #003d4c; + text-decoration: none; +} +div.actions ul li a:hover { + color: #333; + text-decoration: underline; +} + +/* Related */ +div.related { + clear: both; + display: block; +} + +/* Debugging */ +.pr-caller { + color: #000; + background: #c8c; +/* padding-top: 0.2em; */ + padding: 0.1em; +} +pre { + color: #000; + background: #f0f0f0; + padding: 1em; +} +pre.cake-debug { + background: #ffcc00; + font-size: 120%; + line-height: 140%; + margin-top: 1em; + overflow: auto; + position: relative; +} +div.cake-stack-trace { + background: #fff; + border: 4px dotted #ffcc00; + color: #333; + margin: 0px; + padding: 6px; + font-size: 120%; + line-height: 140%; + overflow: auto; + position: relative; +} +div.cake-code-dump pre { + position: relative; + overflow: auto; +} +div.cake-stack-trace pre, div.cake-code-dump pre { + color: #000; + background-color: #F0F0F0; + margin: 0px; + padding: 1em; + overflow: auto; +} +div.cake-code-dump pre, div.cake-code-dump pre code { + clear: both; + font-size: 12px; + line-height: 15px; + margin: 4px 2px; + padding: 4px; + overflow: auto; +} +div.cake-code-dump span.code-highlight { + background-color: #ff0; + padding: 4px; +} +div.code-coverage-results div.code-line { + padding-left:5px; + display:block; + margin-left:10px; +} +div.code-coverage-results div.uncovered span.content { + background:#ecc; +} +div.code-coverage-results div.covered span.content { + background:#cec; +} +div.code-coverage-results div.ignored span.content { + color:#aaa; +} +div.code-coverage-results span.line-num { + color:#666; + display:block; + float:left; + width:20px; + text-align:right; + margin-right:5px; +} +div.code-coverage-results span.line-num strong { + color:#666; +} +div.code-coverage-results div.start { + border:1px solid #aaa; + border-width:1px 1px 0px 1px; + margin-top:30px; + padding-top:5px; +} +div.code-coverage-results div.end { + border:1px solid #aaa; + border-width:0px 1px 1px 1px; + margin-bottom:30px; + padding-bottom:5px; +} +div.code-coverage-results div.realstart { + margin-top:0px; +} +div.code-coverage-results p.note { + color:#bbb; + padding:5px; + margin:5px 0 10px; + font-size:10px; +} +div.code-coverage-results span.result-bad { + color: #a00; +} +div.code-coverage-results span.result-ok { + color: #fa0; +} +div.code-coverage-results span.result-good { + color: #0a0; +} \ No newline at end of file diff --git a/webroot/css/layout.css b/webroot/css/layout.css new file mode 100644 index 0000000..d6392af --- /dev/null +++ b/webroot/css/layout.css @@ -0,0 +1,441 @@ +/************************************************************ + ************************************************************ + * Debug table margin/padding helper + */ + +/* table th, td { border: dashed 1px; } */ +/* .debug-border { border: dashed 1px; } */ + +/************************************************************ + ************************************************************ + * Overall page layout + */ + +table#layout { width: 100% } +td#sidecolumn , +td#pagecolumn { vertical-align: top; } +td#pagecolumn { padding-left: 4mm; } + + +/************************************************************ + ************************************************************ + * Panel captions + */ + +table caption { text-align: left; + font-size: 120%; + font-weight: bold; + padding-bottom: 0.5em; + } + +div.item.list { margin-bottom: 1.5em; } +table.item.list { margin-bottom: 1.5em; } + + +/************************************************************ + ************************************************************ + * Date formats + */ +span.fmt-age.delinquent { color: #f00; } + + +/************************************************************ + ************************************************************ + * Cell configuration + */ + +table.item th , +table.item td { padding: 0.1em 0.4em 0.1em 0.4em; } +table.item td { white-space: nowrap; } +/* table.item { border-spacing: 0 0; /\*IE*\/border-collapse: collapse; empty-cells: show } */ +table.item { border-spacing: 0 0; empty-cells: show } +table.item { border:1px solid #ccc; + border-bottom:2px solid #ccc; + } + +table.list th { background: #dcdcdc; + border-top: 1px solid #fff; + border-left: 1px solid #fff; + border-bottom: 2px solid #aaa; + border-right: 1px solid #ccc; + text-align: center; + } +table.item td { border-right: 1px solid #ccc; } + +tr.evnrow { background: #f4f4f4; } + +/* Generic classes to help with table alignment */ +td.right { text-align: right; } +td.left { text-align: left; } +td.center { text-align: center; } + + +/************************************************************ + ************************************************************ + * Item detail formats + * (such as Customer Info, Unit Info, etc) + */ + +table.detail { width : 60%; + float : left; } +table.detail td.field { width : 10em; } + +table.item.detail td.value { white-space : normal; } + +div.detail.supporting { clear : both; + padding-top: 1.5em; } + +/* Exception for Ledger Entry, which is split over 3 tables */ +.ledger-double-entry { padding-top: 1.0em; + float: left; + clear : left; + width : 60%; +} + +.ledger-double-entry table.item.detail { + width : 45%; +/* border: 2px dashed #0f0; */ +/* margin-left : 5%; */ +/* margin-right : 5%; */ +} + +.ledger-double-entry table.item.detail.debit { +/* margin-left : 2%; */ + float: left; +} + +.ledger-double-entry table.item.detail.credit { +/* margin-right : 2%; */ + float: right; + +} + +/* .ledger-double-entry table.debit { clear: */ + + + +/************************************************************ + ************************************************************ + * Item edit formats + */ + +.edit table.detail { width : 80%; + float : none; + } + + +/************************************************************ + ************************************************************ + * Pertinent Information Box + * These floating boxes appear on the detail pages, + * providing the most pertinent / important information + * for the given view. + */ + +div.infobox { float: right; + margin-top: 2.0em; + margin-right: 0.5em; + } + +table.summary { + margin-left:auto; + margin-right:0.5em; + color: #339; + font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif; + font-size: 125%; + text-align: right; + margin-bottom: 1.5em; + } + +table.summary caption { + font-size: 100%; + text-align: right; + margin-right: 0.6em; + } + +table.summary td { + padding-left:0.5em; + } + + +/************************************************************ + ************************************************************ + * Item table formats + * (listing multiple items of one type) + */ + +/* Item listing width and borders */ +table.list { width : 100%; } + +/* Text alignment exceptions */ +table.list td.id { text-align: center; } +table.list td.currency { text-align: right; } + +/* White spacing exceptions */ +td.comment { white-space: normal; } + +/* Generic column to assign excess space */ +table.list td.slack { width : 99%; } + +/* Customers */ +table.list.customer td.comment { width : 66%; } + +/* Contacts */ +table.list.contact td.comment { width : 66%; } + +/* Contact methods */ +table.list.phone td.preference , +table.list.email td.preference , +table.list.address td.preference { width: 18em; } +table.list.phone td.phone , +table.list.email td.email , +table.list.address td.address { width: 15em; } +table.list.phone td.comment , +table.list.email td.comment , +table.list.address td.comment { width: auto; } + +/* Ledger Entries */ +table.list.ledger td { border-top : 1px dashed #ccc; } +table.list.ledger td.date.receipt { padding-left: 1em; } +table.list.ledger td.evnrow { background: #f4f4f4; } + + +/************************************************************ + ************************************************************ + * Receipt Entry / Tender Edit + */ + +input.payment, div.tender.input input, div.tender.input select { + width: 10em; + } +label.payment { + padding-left: 0.5em; +/* float: left; */ +/* text-align: right; */ +/* display: block; */ + } + +div.tender.edit div.tender.input { + padding-bottom: 0.1em; +} + +div.tender.edit div.tender.input label { + float: left; + width: 12em; + padding-right: 0.5em; + } + + +/************************************************************ + ************************************************************ + * Special cases + */ + +/* Deposit Slips */ +table.list.deposit-slip { width : 60%; + /* REVISIT : 20090701 + * 100% if printing + */ + } +table.list.deposit-slip td.item { width : 30%; } +table.list.deposit-slip td.amount { width : 15%; } + +table.deposit-summary { margin-bottom: 1em; } +table.deposit-summary td { vertical-align: bottom; } +table.deposit-summary td { font-size: 125%; } +table.deposit-summary td.grand.total { border-top : 4px double #000; } +table.deposit-summary td span.dollar-sign { float: left; } +table.deposit-summary td span.dollar-amount { float: right; } +table.deposit-summary td.account { padding-right: 0.8em; } +table.deposit-summary td.quantity { padding-right: 0.8em; } + + +/* Collected Income */ +.account.collected.entry { float : left; + clear : none; } +form#collected-form input[type=button] { float : left; + clear : left; } + +/* NSF items */ +.nsf-tender { text-decoration: line-through; } + + +/************************************************************ + ************************************************************ + * jqGrid + */ + +div.grid.text-below { margin-bottom: 0; } + +span.grid-error { + color: #a00; +} + +span.ui-jqgrid-title { + color: #ffb; + font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif; + font-size: 180%; + margin-bottom: 0.0em; +} + +span.ui-jqgrid-title h2 { + color: #ffb; + font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif; + font-weight: bold; + font-size: 140%; + margin-bottom: 0.0em; +} + + +/************************************************************ + ************************************************************ + * Grid Dynamic Selection Text + */ + +.grid-selection-text { font-size: 150%; + margin-top: 0.4em; + margin-bottom: 1em; } +.grid-selection-text .supporting { font-size: 80%; } + +.grid-selection-text .supporting table { border: none; } +.grid-selection-text .supporting table td.field { width : 5em; } + + + +/************************************************************ + ************************************************************ + * Forms + */ + + +form { + margin-right: 20px; + padding: 0; + width: 80%; +} + +div.dynamic-set fieldset.superset { + margin-top: 1.5em; + width: 80%; +} + +div.dynamic-set fieldset.superset legend { + font-size: 120%; + font-weight: bold; +} + +div.dynamic-set fieldset.superset fieldset { + margin-top: 0.8em; +/* width: 90%; */ +} + +div.dynamic-set fieldset.superset fieldset legend { + font-size: 110%; + font-weight: normal; +} + +fieldset { + border: 1px solid #ccc; + margin-top: 4px; +/* padding: 4px 4px; */ +} +fieldset legend { +/* background:#fff; */ +/* color: #e32; */ +/* font-size: 120%; */ + font-weight: bold; +} +fieldset fieldset { + margin: 4px 4px; +/* margin-bottom: 20px; */ + padding: 4px 4px; +} +fieldset fieldset legend { + font-size: 100%; + font-weight: normal; +} +fieldset fieldset div { + clear: left; +/* margin: 0 20px; */ +} + +/* + * REVISIT : 20090728 + * This "form div" is way too generic, and in fact + * it's screwing up the jqGrid header. I'm commenting + * it out for now, to see if it actually is needed + * anywhere, and hope to delete it in the near future. + */ +/* form div { */ +/* clear: both; */ +/* /\* margin-bottom: 1em; *\/ */ +/* /\* padding: .5em; *\/ */ +/* vertical-align: text-top; */ +/* } */ +form div.input { + color: #444; +} +form div.required { + color: #333; + font-weight: bold; +} +form div.submit { + border: 0; + clear: both; + margin-top: 10px; +/* margin-left: 140px; */ +} + +/************************************************************ + ************************************************************ + * General Style Info + */ + +body { +/* background: #003d4c; */ +/* color: #fff; */ + font-family:'lucida grande',verdana,helvetica,arial,sans-serif; + font-size:90%; + margin: 0; +} +a { + color: #003d4c; + text-decoration: underline; + font-weight: bold; +} +a:hover { + color: #00f; + text-decoration:none; +} +a img { + border:none; +} + +h1, h2, h3, h4 { + font-weight: normal; +} + +h1 { + color: #003d4c; + font-size: 100%; + margin: 0.1em 0; +} +h2 { +/* color: #e32; */ + color: #993; + font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif; + font-size: 190%; + margin-bottom: 0.3em; +} +h3 { + color: #993; + font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif; +/* font-size: 165%; */ +} +h4 { + color: #993; + font-weight: normal; + padding-top: 0.5em; +} + + diff --git a/webroot/css/print.css b/webroot/css/print.css new file mode 100644 index 0000000..7c3058c --- /dev/null +++ b/webroot/css/print.css @@ -0,0 +1,46 @@ +/************************************************************ + ************************************************************ + * Styles for media type: print + */ + +/* No need for the menu */ +table#layout td#sidecolumn + { display: none; } + +/* No need for the debug kit */ +div#debug-kit-toolbar + { display: none; } + +/* In fact, no need for any debug stuff */ +.debug + { display: none; } + + +/************************************************************ + * Grid display + */ + +/* Grid are not to be truncated when printing, so + * set overflow to visible for necessary selectors. + */ +div#content, div.ui-jqgrid-bdiv + { overflow: visible ! important } + +div.ui-jqgrid-hdiv + { border-bottom: 3px double #000; } + +/* The header is generally useless, except for the , + * as well as the footer navtable. The pagination buttons + * are of no use, nor is the selbox to set # of rows. + */ +.GridHeader td, +div.scroll .navtable, +div.scroll .ui-pg-button, +div.scroll select.ui-pg-selbox + { display: none; } + +/* The "page N / M" input box... make it look like normal text */ +div.scroll input[type='text'] { + border: none; + background: transparent; +} diff --git a/webroot/css/sidemenu.css b/webroot/css/sidemenu.css new file mode 100644 index 0000000..c141e61 --- /dev/null +++ b/webroot/css/sidemenu.css @@ -0,0 +1,40 @@ +/************************************************************ + ************************************************************ + * Side Menu Layout + */ + +td#sidecolumn { width: 10em; background: lightblue; text-align: left; } + +td#sidecolumn .header , +td#sidecolumn .item { white-space : nowrap; } + + +/************************************************************ + ************************************************************ + * Menu Headers + */ + +td#sidecolumn .header { margin-top: 0.4em; + background: blue; + color: white; + font-weight: bold; + } + + + +/************************************************************ + ************************************************************ + * Menu Separators + */ + +td#sidecolumn hr { margin-top: 0.4em; margin-bottom: 0.3em; } + + + +/************************************************************ + ************************************************************ + * Menu Items + */ + +td#sidecolumn .item { padding-left: 0.6em; } + diff --git a/webroot/css/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png b/webroot/css/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000..5b5dab2 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/webroot/css/themes/base/images/ui-bg_flat_75_ffffff_40x100.png b/webroot/css/themes/base/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 0000000..ac8b229 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/webroot/css/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png b/webroot/css/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000..ad3d634 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/webroot/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png b/webroot/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 0000000..42ccba2 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/webroot/css/themes/base/images/ui-bg_glass_75_dadada_1x400.png b/webroot/css/themes/base/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000..5a46b47 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/webroot/css/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png b/webroot/css/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000..86c2baa Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/webroot/css/themes/base/images/ui-bg_glass_75_ffffff_1x400.png b/webroot/css/themes/base/images/ui-bg_glass_75_ffffff_1x400.png new file mode 100644 index 0000000..e65ca12 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_glass_75_ffffff_1x400.png differ diff --git a/webroot/css/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png b/webroot/css/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000..4443fdc Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/webroot/css/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/webroot/css/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000..7c9fa6c Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/webroot/css/themes/base/images/ui-bg_inset-soft_95_fef1ec_1x100.png b/webroot/css/themes/base/images/ui-bg_inset-soft_95_fef1ec_1x100.png new file mode 100644 index 0000000..0e05810 Binary files /dev/null and b/webroot/css/themes/base/images/ui-bg_inset-soft_95_fef1ec_1x100.png differ diff --git a/webroot/css/themes/base/images/ui-icons_222222_256x240.png b/webroot/css/themes/base/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..ee039dc Binary files /dev/null and b/webroot/css/themes/base/images/ui-icons_222222_256x240.png differ diff --git a/webroot/css/themes/base/images/ui-icons_2e83ff_256x240.png b/webroot/css/themes/base/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..45e8928 Binary files /dev/null and b/webroot/css/themes/base/images/ui-icons_2e83ff_256x240.png differ diff --git a/webroot/css/themes/base/images/ui-icons_454545_256x240.png b/webroot/css/themes/base/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000..7ec70d1 Binary files /dev/null and b/webroot/css/themes/base/images/ui-icons_454545_256x240.png differ diff --git a/webroot/css/themes/base/images/ui-icons_888888_256x240.png b/webroot/css/themes/base/images/ui-icons_888888_256x240.png new file mode 100644 index 0000000..5ba708c Binary files /dev/null and b/webroot/css/themes/base/images/ui-icons_888888_256x240.png differ diff --git a/webroot/css/themes/base/images/ui-icons_cd0a0a_256x240.png b/webroot/css/themes/base/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..7930a55 Binary files /dev/null and b/webroot/css/themes/base/images/ui-icons_cd0a0a_256x240.png differ diff --git a/webroot/css/themes/base/ui.accordion.css b/webroot/css/themes/base/ui.accordion.css new file mode 100644 index 0000000..ee1b1b6 --- /dev/null +++ b/webroot/css/themes/base/ui.accordion.css @@ -0,0 +1,9 @@ +/* Accordion +----------------------------------*/ +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; } +.ui-accordion .ui-accordion-content-active { display: block; } \ No newline at end of file diff --git a/webroot/css/themes/base/ui.all.css b/webroot/css/themes/base/ui.all.css new file mode 100644 index 0000000..543e4c3 --- /dev/null +++ b/webroot/css/themes/base/ui.all.css @@ -0,0 +1,2 @@ +@import "ui.base.css"; +@import "ui.theme.css"; diff --git a/webroot/css/themes/base/ui.base.css b/webroot/css/themes/base/ui.base.css new file mode 100644 index 0000000..d716095 --- /dev/null +++ b/webroot/css/themes/base/ui.base.css @@ -0,0 +1,8 @@ +@import url("ui.core.css"); +@import url("ui.resizable.css"); +@import url("ui.accordion.css"); +@import url("ui.dialog.css"); +@import url("ui.slider.css"); +@import url("ui.tabs.css"); +@import url("ui.datepicker.css"); +@import url("ui.progressbar.css"); \ No newline at end of file diff --git a/webroot/css/themes/base/ui.core.css b/webroot/css/themes/base/ui.core.css new file mode 100644 index 0000000..c2f18f2 --- /dev/null +++ b/webroot/css/themes/base/ui.core.css @@ -0,0 +1,37 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } \ No newline at end of file diff --git a/webroot/css/themes/base/ui.datepicker.css b/webroot/css/themes/base/ui.datepicker.css new file mode 100644 index 0000000..567f8c9 --- /dev/null +++ b/webroot/css/themes/base/ui.datepicker.css @@ -0,0 +1,62 @@ +/* Datepicker +----------------------------------*/ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; } +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +} \ No newline at end of file diff --git a/webroot/css/themes/base/ui.dialog.css b/webroot/css/themes/base/ui.dialog.css new file mode 100644 index 0000000..2997595 --- /dev/null +++ b/webroot/css/themes/base/ui.dialog.css @@ -0,0 +1,13 @@ +/* Dialog +----------------------------------*/ +.ui-dialog { position: relative; padding: .2em; width: 300px; } +.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } diff --git a/webroot/css/themes/base/ui.progressbar.css b/webroot/css/themes/base/ui.progressbar.css new file mode 100644 index 0000000..bc0939e --- /dev/null +++ b/webroot/css/themes/base/ui.progressbar.css @@ -0,0 +1,4 @@ +/* Progressbar +----------------------------------*/ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/webroot/css/themes/base/ui.resizable.css b/webroot/css/themes/base/ui.resizable.css new file mode 100644 index 0000000..44efeb2 --- /dev/null +++ b/webroot/css/themes/base/ui.resizable.css @@ -0,0 +1,13 @@ +/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;} \ No newline at end of file diff --git a/webroot/css/themes/base/ui.slider.css b/webroot/css/themes/base/ui.slider.css new file mode 100644 index 0000000..4c56218 --- /dev/null +++ b/webroot/css/themes/base/ui.slider.css @@ -0,0 +1,17 @@ +/* Slider +----------------------------------*/ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; } \ No newline at end of file diff --git a/webroot/css/themes/base/ui.tabs.css b/webroot/css/themes/base/ui.tabs.css new file mode 100644 index 0000000..3ca6b9a --- /dev/null +++ b/webroot/css/themes/base/ui.tabs.css @@ -0,0 +1,11 @@ +/* Tabs +----------------------------------*/ +.ui-tabs { padding: .2em; zoom: 1; } +.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; } +.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/webroot/css/themes/base/ui.theme.css b/webroot/css/themes/base/ui.theme.css new file mode 100644 index 0000000..73ed788 --- /dev/null +++ b/webroot/css/themes/base/ui.theme.css @@ -0,0 +1,245 @@ + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://jqueryui.com/themeroller/ +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(images/ui-bg_flat_75_ffffff_40x100.png)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; } +.ui-widget-content a { color: #222222/*{fcContent}*/; } +.ui-widget-header { border: 1px solid #aaaaaa/*{borderColorHeader}*/; background: #cccccc/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #222222/*{fcHeader}*/; font-weight: bold; } +.ui-widget-header a { color: #222222/*{fcHeader}*/; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; outline: none; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; outline: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; outline: none; } +.ui-state-hover a, .ui-state-hover a:hover { color: #212121/*{fcHover}*/; text-decoration: none; outline: none; } +.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; outline: none; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; outline: none; text-decoration: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636/*{fcHighlight}*/; } +.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; } +.ui-state-error a, .ui-widget-content .ui-state-error a { color: #cd0a0a/*{fcError}*/; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a/*{fcError}*/; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/; } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/; } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; } +.ui-corner-tr { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; } +.ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-top { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-right { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-left { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; } +.ui-corner-all { -moz-border-radius: 4px/*{cornerRadius}*/; -webkit-border-radius: 4px/*{cornerRadius}*/; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; } +.ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRadiusShadow}*/; } \ No newline at end of file diff --git a/webroot/css/themes/dotluv/images/ui-bg_diagonals-thick_15_0b3e6f_40x40.png b/webroot/css/themes/dotluv/images/ui-bg_diagonals-thick_15_0b3e6f_40x40.png new file mode 100644 index 0000000..44d890a Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_diagonals-thick_15_0b3e6f_40x40.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_dots-medium_30_0b58a2_4x4.png b/webroot/css/themes/dotluv/images/ui-bg_dots-medium_30_0b58a2_4x4.png new file mode 100644 index 0000000..fa6c9cb Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_dots-medium_30_0b58a2_4x4.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_dots-small_20_333333_2x2.png b/webroot/css/themes/dotluv/images/ui-bg_dots-small_20_333333_2x2.png new file mode 100644 index 0000000..b90acb9 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_dots-small_20_333333_2x2.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_dots-small_30_a32d00_2x2.png b/webroot/css/themes/dotluv/images/ui-bg_dots-small_30_a32d00_2x2.png new file mode 100644 index 0000000..1e4b105 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_dots-small_30_a32d00_2x2.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_dots-small_40_00498f_2x2.png b/webroot/css/themes/dotluv/images/ui-bg_dots-small_40_00498f_2x2.png new file mode 100644 index 0000000..fa35313 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_dots-small_40_00498f_2x2.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_flat_0_aaaaaa_40x100.png b/webroot/css/themes/dotluv/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000..5b5dab2 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_flat_40_292929_40x100.png b/webroot/css/themes/dotluv/images/ui-bg_flat_40_292929_40x100.png new file mode 100644 index 0000000..70bf37a Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_flat_40_292929_40x100.png differ diff --git a/webroot/css/themes/dotluv/images/ui-bg_gloss-wave_20_111111_500x100.png b/webroot/css/themes/dotluv/images/ui-bg_gloss-wave_20_111111_500x100.png new file mode 100644 index 0000000..60fcd81 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-bg_gloss-wave_20_111111_500x100.png differ diff --git a/webroot/css/themes/dotluv/images/ui-icons_00498f_256x240.png b/webroot/css/themes/dotluv/images/ui-icons_00498f_256x240.png new file mode 100644 index 0000000..9a4b67b Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-icons_00498f_256x240.png differ diff --git a/webroot/css/themes/dotluv/images/ui-icons_98d2fb_256x240.png b/webroot/css/themes/dotluv/images/ui-icons_98d2fb_256x240.png new file mode 100644 index 0000000..b077584 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-icons_98d2fb_256x240.png differ diff --git a/webroot/css/themes/dotluv/images/ui-icons_9ccdfc_256x240.png b/webroot/css/themes/dotluv/images/ui-icons_9ccdfc_256x240.png new file mode 100644 index 0000000..fc3dbb9 Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-icons_9ccdfc_256x240.png differ diff --git a/webroot/css/themes/dotluv/images/ui-icons_ffffff_256x240.png b/webroot/css/themes/dotluv/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..746e6fa Binary files /dev/null and b/webroot/css/themes/dotluv/images/ui-icons_ffffff_256x240.png differ diff --git a/webroot/css/themes/dotluv/ui.all.css b/webroot/css/themes/dotluv/ui.all.css new file mode 100644 index 0000000..67df307 --- /dev/null +++ b/webroot/css/themes/dotluv/ui.all.css @@ -0,0 +1,406 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Arial,%20sans-serif&fwDefault=bold&fsDefault=1.3em&cornerRadius=4px&bgColorHeader=0b3e6f&bgTextureHeader=08_diagonals_thick.png&bgImgOpacityHeader=15&borderColorHeader=0b3e6f&fcHeader=f6f6f6&iconColorHeader=98d2fb&bgColorContent=111111&bgTextureContent=12_gloss_wave.png&bgImgOpacityContent=20&borderColorContent=000000&fcContent=d9d9d9&iconColorContent=9ccdfc&bgColorDefault=333333&bgTextureDefault=09_dots_small.png&bgImgOpacityDefault=20&borderColorDefault=333333&fcDefault=ffffff&iconColorDefault=9ccdfc&bgColorHover=00498f&bgTextureHover=09_dots_small.png&bgImgOpacityHover=40&borderColorHover=222222&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=292929&bgTextureActive=01_flat.png&bgImgOpacityActive=40&borderColorActive=096ac8&fcActive=75abff&iconColorActive=00498f&bgColorHighlight=0b58a2&bgTextureHighlight=10_dots_medium.png&bgImgOpacityHighlight=30&borderColorHighlight=052f57&fcHighlight=ffffff&iconColorHighlight=ffffff&bgColorError=a32d00&bgTextureError=09_dots_small.png&bgImgOpacityError=30&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffffff&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Arial, sans-serif; font-size: 1.3em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #000000; background: #111111 url(images/ui-bg_gloss-wave_20_111111_500x100.png) 50% top repeat-x; color: #d9d9d9; } +.ui-widget-content a { color: #d9d9d9; } +.ui-widget-header { border: 1px solid #0b3e6f; background: #0b3e6f url(images/ui-bg_diagonals-thick_15_0b3e6f_40x40.png) 50% 50% repeat; color: #f6f6f6; font-weight: bold; } +.ui-widget-header a { color: #f6f6f6; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #333333; background: #333333 url(images/ui-bg_dots-small_20_333333_2x2.png) 50% 50% repeat; font-weight: bold; color: #ffffff; outline: none; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; outline: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #222222; background: #00498f url(images/ui-bg_dots-small_40_00498f_2x2.png) 50% 50% repeat; font-weight: bold; color: #ffffff; outline: none; } +.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; outline: none; } +.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #096ac8; background: #292929 url(images/ui-bg_flat_40_292929_40x100.png) 50% 50% repeat-x; font-weight: bold; color: #75abff; outline: none; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #75abff; outline: none; text-decoration: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #052f57; background: #0b58a2 url(images/ui-bg_dots-medium_30_0b58a2_4x4.png) 50% 50% repeat; color: #ffffff; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #ffffff; } +.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #a32d00 url(images/ui-bg_dots-small_30_a32d00_2x2.png) 50% 50% repeat; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #ffffff; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_9ccdfc_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_9ccdfc_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_98d2fb_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_9ccdfc_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_00498f_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }/* Accordion +----------------------------------*/ +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; } +.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker +----------------------------------*/ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; } +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* Dialog +----------------------------------*/ +.ui-dialog { position: relative; padding: .2em; width: 300px; } +.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* Progressbar +----------------------------------*/ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider +----------------------------------*/ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs +----------------------------------*/ +.ui-tabs { padding: .2em; zoom: 1; } +.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; } +.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/webroot/css/themes/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/webroot/css/themes/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000..5b5dab2 Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/webroot/css/themes/smoothness/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 0000000..ac8b229 Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/webroot/css/themes/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000..b39a6fb Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/webroot/css/themes/smoothness/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 0000000..42ccba2 Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/webroot/css/themes/smoothness/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000..5a46b47 Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png b/webroot/css/themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000..38df73a Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png b/webroot/css/themes/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000..4443fdc Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/webroot/css/themes/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/webroot/css/themes/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000..7c9fa6c Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/webroot/css/themes/smoothness/images/ui-icons_222222_256x240.png b/webroot/css/themes/smoothness/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..ee039dc Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-icons_222222_256x240.png differ diff --git a/webroot/css/themes/smoothness/images/ui-icons_2e83ff_256x240.png b/webroot/css/themes/smoothness/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..45e8928 Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-icons_2e83ff_256x240.png differ diff --git a/webroot/css/themes/smoothness/images/ui-icons_454545_256x240.png b/webroot/css/themes/smoothness/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000..fe1085b Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-icons_454545_256x240.png differ diff --git a/webroot/css/themes/smoothness/images/ui-icons_888888_256x240.png b/webroot/css/themes/smoothness/images/ui-icons_888888_256x240.png new file mode 100644 index 0000000..5ba708c Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-icons_888888_256x240.png differ diff --git a/webroot/css/themes/smoothness/images/ui-icons_cd0a0a_256x240.png b/webroot/css/themes/smoothness/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..7930a55 Binary files /dev/null and b/webroot/css/themes/smoothness/images/ui-icons_cd0a0a_256x240.png differ diff --git a/webroot/css/themes/smoothness/ui.all.css b/webroot/css/themes/smoothness/ui.all.css new file mode 100644 index 0000000..444486b --- /dev/null +++ b/webroot/css/themes/smoothness/ui.all.css @@ -0,0 +1,406 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; } +.ui-widget-header a { color: #222222; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; outline: none; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; outline: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; } +.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; outline: none; } +.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; outline: none; text-decoration: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }/* Accordion +----------------------------------*/ +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; } +.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker +----------------------------------*/ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; } +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* Dialog +----------------------------------*/ +.ui-dialog { position: relative; padding: .2em; width: 300px; } +.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* Progressbar +----------------------------------*/ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider +----------------------------------*/ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs +----------------------------------*/ +.ui-tabs { padding: .2em; zoom: 1; } +.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; } +.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/webroot/css/themes/start/images/ui-bg_flat_55_999999_40x100.png b/webroot/css/themes/start/images/ui-bg_flat_55_999999_40x100.png new file mode 100644 index 0000000..6b6de7d Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_flat_55_999999_40x100.png differ diff --git a/webroot/css/themes/start/images/ui-bg_flat_75_aaaaaa_40x100.png b/webroot/css/themes/start/images/ui-bg_flat_75_aaaaaa_40x100.png new file mode 100644 index 0000000..5b5dab2 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_flat_75_aaaaaa_40x100.png differ diff --git a/webroot/css/themes/start/images/ui-bg_glass_45_0078ae_1x400.png b/webroot/css/themes/start/images/ui-bg_glass_45_0078ae_1x400.png new file mode 100644 index 0000000..3dac650 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_glass_45_0078ae_1x400.png differ diff --git a/webroot/css/themes/start/images/ui-bg_glass_55_f8da4e_1x400.png b/webroot/css/themes/start/images/ui-bg_glass_55_f8da4e_1x400.png new file mode 100644 index 0000000..b383704 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_glass_55_f8da4e_1x400.png differ diff --git a/webroot/css/themes/start/images/ui-bg_glass_75_79c9ec_1x400.png b/webroot/css/themes/start/images/ui-bg_glass_75_79c9ec_1x400.png new file mode 100644 index 0000000..d384e42 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_glass_75_79c9ec_1x400.png differ diff --git a/webroot/css/themes/start/images/ui-bg_gloss-wave_45_e14f1c_500x100.png b/webroot/css/themes/start/images/ui-bg_gloss-wave_45_e14f1c_500x100.png new file mode 100644 index 0000000..b9851ba Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_gloss-wave_45_e14f1c_500x100.png differ diff --git a/webroot/css/themes/start/images/ui-bg_gloss-wave_50_6eac2c_500x100.png b/webroot/css/themes/start/images/ui-bg_gloss-wave_50_6eac2c_500x100.png new file mode 100644 index 0000000..76dac56 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_gloss-wave_50_6eac2c_500x100.png differ diff --git a/webroot/css/themes/start/images/ui-bg_gloss-wave_75_2191c0_500x100.png b/webroot/css/themes/start/images/ui-bg_gloss-wave_75_2191c0_500x100.png new file mode 100644 index 0000000..eeacf69 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_gloss-wave_75_2191c0_500x100.png differ diff --git a/webroot/css/themes/start/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/webroot/css/themes/start/images/ui-bg_inset-hard_100_fcfdfd_1x100.png new file mode 100644 index 0000000..38c3833 Binary files /dev/null and b/webroot/css/themes/start/images/ui-bg_inset-hard_100_fcfdfd_1x100.png differ diff --git a/webroot/css/themes/start/images/ui-icons_0078ae_256x240.png b/webroot/css/themes/start/images/ui-icons_0078ae_256x240.png new file mode 100644 index 0000000..58f96f8 Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_0078ae_256x240.png differ diff --git a/webroot/css/themes/start/images/ui-icons_056b93_256x240.png b/webroot/css/themes/start/images/ui-icons_056b93_256x240.png new file mode 100644 index 0000000..8e6103d Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_056b93_256x240.png differ diff --git a/webroot/css/themes/start/images/ui-icons_d8e7f3_256x240.png b/webroot/css/themes/start/images/ui-icons_d8e7f3_256x240.png new file mode 100644 index 0000000..2c8aac4 Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_d8e7f3_256x240.png differ diff --git a/webroot/css/themes/start/images/ui-icons_e0fdff_256x240.png b/webroot/css/themes/start/images/ui-icons_e0fdff_256x240.png new file mode 100644 index 0000000..d985a26 Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_e0fdff_256x240.png differ diff --git a/webroot/css/themes/start/images/ui-icons_f5e175_256x240.png b/webroot/css/themes/start/images/ui-icons_f5e175_256x240.png new file mode 100644 index 0000000..7862520 Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_f5e175_256x240.png differ diff --git a/webroot/css/themes/start/images/ui-icons_f7a50d_256x240.png b/webroot/css/themes/start/images/ui-icons_f7a50d_256x240.png new file mode 100644 index 0000000..2ed57ef Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_f7a50d_256x240.png differ diff --git a/webroot/css/themes/start/images/ui-icons_fcd113_256x240.png b/webroot/css/themes/start/images/ui-icons_fcd113_256x240.png new file mode 100644 index 0000000..68dcff5 Binary files /dev/null and b/webroot/css/themes/start/images/ui-icons_fcd113_256x240.png differ diff --git a/webroot/css/themes/start/ui.all.css b/webroot/css/themes/start/ui.all.css new file mode 100644 index 0000000..d30294c --- /dev/null +++ b/webroot/css/themes/start/ui.all.css @@ -0,0 +1,406 @@ +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +*/ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + + +/* +* jQuery UI CSS Framework +* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=2191c0&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=75&borderColorHeader=4297d7&fcHeader=eaf5f7&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=0078ae&bgColorDefault=0078ae&bgTextureDefault=02_glass.png&bgImgOpacityDefault=45&borderColorDefault=77d5f7&fcDefault=ffffff&iconColorDefault=e0fdff&bgColorHover=79c9ec&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=448dae&fcHover=026890&iconColorHover=056b93&bgColorActive=6eac2c&bgTextureActive=12_gloss_wave.png&bgImgOpacityActive=50&borderColorActive=acdd4a&fcActive=ffffff&iconColorActive=f5e175&bgColorHighlight=f8da4e&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcd113&fcHighlight=915608&iconColorHighlight=f7a50d&bgColorError=e14f1c&bgTextureError=12_gloss_wave.png&bgImgOpacityError=45&borderColorError=cd0a0a&fcError=ffffff&iconColorError=fcd113&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=999999&bgTextureShadow=01_flat.png&bgImgOpacityShadow=55&opacityShadow=45&thicknessShadow=0px&offsetTopShadow=5px&offsetLeftShadow=5px&cornerRadiusShadow=5px +*/ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #4297d7; background: #2191c0 url(images/ui-bg_gloss-wave_75_2191c0_500x100.png) 50% 50% repeat-x; color: #eaf5f7; font-weight: bold; } +.ui-widget-header a { color: #eaf5f7; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #77d5f7; background: #0078ae url(images/ui-bg_glass_45_0078ae_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; outline: none; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; outline: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #448dae; background: #79c9ec url(images/ui-bg_glass_75_79c9ec_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #026890; outline: none; } +.ui-state-hover a, .ui-state-hover a:hover { color: #026890; text-decoration: none; outline: none; } +.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #acdd4a; background: #6eac2c url(images/ui-bg_gloss-wave_50_6eac2c_500x100.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; outline: none; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ffffff; outline: none; text-decoration: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcd113; background: #f8da4e url(images/ui-bg_glass_55_f8da4e_1x400.png) 50% 50% repeat-x; color: #915608; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #915608; } +.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #e14f1c url(images/ui-bg_gloss-wave_45_e14f1c_500x100.png) 50% top repeat-x; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #ffffff; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_0078ae_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_0078ae_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_e0fdff_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_056b93_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_f5e175_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_f7a50d_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_fcd113_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; } +.ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; } +.ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; } +.ui-corner-top { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; } +.ui-corner-right { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; } +.ui-corner-left { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; } +.ui-corner-all { -moz-border-radius: 5px; -webkit-border-radius: 5px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_75_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: 5px 0 0 5px; padding: 0px; background: #999999 url(images/ui-bg_flat_55_999999_40x100.png) 50% 50% repeat-x; opacity: .45;filter:Alpha(Opacity=45); -moz-border-radius: 5px; -webkit-border-radius: 5px; }/* Accordion +----------------------------------*/ +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; } +.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker +----------------------------------*/ +.ui-datepicker { width: 17em; padding: .2em .2em 0; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; } +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* Dialog +----------------------------------*/ +.ui-dialog { position: relative; padding: .2em; width: 300px; } +.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* Progressbar +----------------------------------*/ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable +----------------------------------*/ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider +----------------------------------*/ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs +----------------------------------*/ +.ui-tabs { padding: .2em; zoom: 1; } +.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; } +.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/webroot/css/ui.jqgrid.css b/webroot/css/ui.jqgrid.css new file mode 100644 index 0000000..d89ae46 --- /dev/null +++ b/webroot/css/ui.jqgrid.css @@ -0,0 +1,2 @@ +.ui-jqgrid{position:relative;font-size:11px;}.ui-jqgrid .ui-jqgrid-view{position:relative;left:0;top:0;padding:.0em;}.ui-jqgrid .ui-jqgrid-titlebar{padding:.3em .2em .2em .3em;position:relative;border-left:0 none;border-right:0 none;border-top:0 none;}.ui-jqgrid .ui-jqgrid-title{float:left;margin:.1em 0 .2em;}.ui-jqgrid .ui-jqgrid-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px;}.ui-jqgrid .ui-jqgrid-titlebar-close span{display:block;margin:1px;}.ui-jqgrid .ui-jqgrid-titlebar-close:hover{padding:0;}.ui-jqgrid .ui-jqgrid-hdiv{position:relative;margin:0;padding:0;overflow:hidden;border-left:0 none!important;border-top:0 none!important;border-right:0 none!important;}.ui-jqgrid .ui-jqgrid-hbox{float:left;padding-right:20px;}.ui-jqgrid .ui-jqgrid-htable{table-layout:fixed;margin:0;}.ui-jqgrid .ui-jqgrid-htable th{height:22px;padding:0 2px 0 2px;}.ui-jqgrid .ui-jqgrid-htable th div{overflow:hidden;position:relative;height:16px;}.ui-th-column,.ui-jqgrid .ui-jqgrid-htable th.ui-th-column{overflow:hidden;white-space:nowrap;text-align:center;border-top:0 none;border-left:0 none;border-bottom:0 none;}.ui-jqgrid .ui-th-div-ie{float:left;width:97%;white-space:nowrap;}.ui-jqgrid .ui-jqgrid-resize{height:20px!important;position:relative;cursor:e-resize;cursor:col-resize;float:right;display:inline;margin:-2px -2px -2px 0;overflow:hidden;}.ui-jqgrid .ui-grid-ico-sort{float:right;overflow:hidden;position:absolute;display:inline;cursor:pointer!important;}.ui-jqgrid .ui-icon-asc{margin-top:-3px;height:12px;}.ui-jqgrid .ui-icon-desc{margin-top:3px;height:12px;}.ui-jqgrid .ui-i-asc{margin-top:0;height:16px;}.ui-jqgrid .ui-i-desc{margin-top:0;margin-left:13px;height:16px;}.ui-jqgrid .ui-jqgrid-sortable{cursor:pointer;}.ui-jqgrid tr.ui-search-toolbar th{border-top-width:1px!important;border-top-color:inherit!important;border-top-style:ridge!important;}.ui-jqgrid .ui-jqgrid-bdiv{position:relative;margin:0;padding:0;overflow:auto;}.ui-jqgrid .ui-jqgrid-btable{table-layout:fixed;margin-left:0;}.ui-jqgrid tr.jqgrow td{border-right-width:1px;border-right-color:inherit;border-right-style:solid;border-bottom-width:1px;border-bottom-color:inherit;border-bottom-style:solid;}.ui-jqgrid tr.jqgrow td{font-weight:normal;overflow:hidden;text-align:left;white-space:pre;height:22px;padding:0 2px 0 2px;}.ui-jqgrid td.jqgrid-rownum{padding:0 2px 0 2px;margin:0;border:0 none;}.ui-jqgrid .ui-jqgrid-resize-mark{width:2px;left:0;background-color:#777;cursor:e-resize;cursor:col-resize;position:absolute;top:0;height:100px;overflow:hidden;display:none;border:0 none;}.ui-jqgrid .ui-jqgrid-sdiv{position:relative;margin:0;padding:0;overflow:hidden;border-left:0 none!important;border-top:0 none!important;border-right:0 none!important;}.ui-jqgrid .ui-jqgrid-ftable{table-layout:fixed;}.ui-jqgrid tr.footrow td{border-right-width:1px;border-right-color:inherit;border-right-style:solid;border-top-width:1px;border-top-color:inherit;border-top-style:solid;}.ui-jqgrid tr.footrow td{font-weight:bold;overflow:hidden;text-align:left;white-space:nowrap;height:21px;padding:0 2px 0 2px;}.ui-jqgrid .ui-jqgrid-pager{border-left:0 none!important;border-right:0 none!important;border-bottom:0 none!important;margin:0!important;padding:0!important;position:relative;height:25px;white-space:nowrap;overflow:hidden;}.ui-jqgrid .ui-pager-control{position:relative;}.ui-jqgrid .ui-pg-table{position:relative;padding-bottom:2px;}.ui-jqgrid .ui-pg-table td{font-weight:normal;vertical-align:middle;padding:1px;overflow:hidden;}.ui-jqgrid .ui-pg-button{height:19px!important;}.ui-jqgrid .ui-pg-button span{display:block;margin:1px;float:left;}.ui-jqgrid .ui-pg-button:hover{padding:0;}.ui-jqgrid .ui-state-disabled:hover{padding:1px;}.ui-jqgrid .ui-pg-input{height:13px;font-size:.8em;}.ui-jqgrid .ui-pg-selbox{font-size:.8em;line-height:18px;display:block;height:18px;}.ui-jqgrid .ui-separator{height:18px;border-left:1px solid #ccc;border-right:1px solid #ccc;margin:1px;float:right;}.ui-jqgrid .ui-paging-info{font-weight:normal;height:19px;margin-top:3px;margin-right:4px;}.ui-jqgrid .ui-jqgrid-pager .ui-pg-div{padding:1px 0;cursor:pointer;float:left;list-style-image:none;list-style-position:outside;list-style-type:none;position:relative;}.ui-jqgrid .ui-jqgrid-pager .ui-pg-div span.ui-icon{float:left;margin:0 2px;}.ui-jqgrid .ui-jqgrid-btable .ui-sgcollapsed span{display:block;}.ui-jqgrid .ui-subgrid{margin:0;padding:0;width:100%;}.ui-jqgrid .ui-subgrid table{table-layout:fixed;}.ui-jqgrid .ui-subgrid tr.ui-subtblcell td{height:18px;border-right-width:1px;border-right-color:inherit;border-right-style:solid;border-bottom-width:1px;border-bottom-color:inherit;border-bottom-style:solid;}.ui-jqgrid .ui-subgrid td.subgrid-data{border-top:0 none!important;}.ui-jqgrid .loading{position:absolute;top:45%;left:45%;width:auto;z-index:101;padding:6px;margin:5px;text-align:center;font-weight:bold;display:none;border-width:2px!important;}.ui-jqgrid .jqgrid-overlay{display:none;z-index:100;}* html .jqgrid-overlay{width:expression(this.parentNode.offsetWidth+'px');height:expression(this.parentNode.offsetHeight+'px');}* .jqgrid-overlay iframe{position:absolute;top:0;left:0;z-index:-1;width:expression(this.parentNode.offsetWidth+'px');height:expression(this.parentNode.offsetHeight+'px');}.ui-jqgrid .ui-userdata{border-left:0 none;border-right:0 none;height:21px;overflow:hidden;}.ui-jqdialog{display:none;width:300px;position:absolute;padding:.2em;font-size:11px;overflow:visible;}.ui-jqdialog .ui-jqdialog-titlebar{padding:.3em .2em;position:relative;}.ui-jqdialog .ui-jqdialog-title{float:left;margin:.1em 0 .2em;}.ui-jqdialog .ui-jqdialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px;}.ui-jqdialog .ui-jqdialog-titlebar-close span{display:block;margin:1px;}.ui-jqdialog .ui-jqdialog-titlebar-close:hover,.ui-jqdialog .ui-jqdialog-titlebar-close:focus{padding:0;}.ui-jqdialog-content,.ui-jqdialog .ui-jqdialog-content{border:0;padding:.3em .2em;background:none;height:auto;}.ui-jqdialog .ui-jqconfirm{padding:.4em 1em;border-width:3px;position:absolute;bottom:10px;right:10px;overflow:visible;display:none;height:80px;width:220px;text-align:center;}.ui-jqdialog-content .FormGrid{margin:0;}.ui-jqdialog-content .EditTable{width:100%;}.ui-jqdialog-content td.EditButton{text-align:right;border-top:0 none;border-left:0 none;border-right:0 none;padding-bottom:5px;padding-top:5px;}.ui-jqdialog-content td.navButton{text-align:center;border-left:0 none;border-top:0 none;border-right:0 none;padding-bottom:5px;padding-top:5px;}.ui-jqdialog-content .CaptionTD{text-align:left;vertical-align:top;border-left:0 none;border-right:0 none;border-bottom:0 none;padding:1px;white-space:nowrap;}.ui-jqdialog-content .DataTD{padding:1px;border-left:0 none;border-right:0 none;border-bottom:0 none;vertical-align:top;}.fm-button{margin:0 4px 0 0;padding:.4em .5em;text-decoration:none!important;cursor:pointer;position:relative;text-align:center;zoom:1;}.fm-button-icon-left{padding-left:1.9em;}.fm-button-icon-right{padding-right:1.9em;}.fm-button-icon-left .ui-icon{right:auto;left:.2em;margin-left:0;position:absolute;top:50%;margin-top:-8px;}.fm-button-icon-right .ui-icon{left:auto;right:.2em;margin-left:0;position:absolute;top:50%;margin-top:-8px;}#nData,#pData{float:left;margin:3px;padding:0;display:block;width:15px;}.ui-jqgrid .selected-row,.ui-jqgrid .selected-row td{font-style:normal;border:0 none;}.ui-jqgrid .tree-wrap{float:left;position:relative;height:18px;white-space:nowrap;overflow:hidden;}.ui-jqgrid .tree-minus{position:absolute;height:18px;width:18px;overflow:hidden;}.ui-jqgrid .tree-plus{position:absolute;height:18px;width:18px;overflow:hidden;}.ui-jqgrid .tree-leaf{position:absolute;height:18px;width:18px;overflow:hidden;}.ui-jqgrid .treeclick{cursor:pointer;}.jqmOverlay{background-color:#000;}* iframe.jqm{position:absolute;top:0;left:0;z-index:-1;width:expression(this.parentNode.offsetWidth+'px');height:expression(this.parentNode.offsetHeight+'px');} +.ui-searchFilter{display:none;position:absolute;z-index:100;width:100%;height:100%;overflow:visible;}.ui-searchFilter table{position:relative;}.ui-searchFilter .ui-state-default{cursor:pointer;}.ui-searchFilter .divider{height:1px;}.ui-searchFilter .divider div{background-color:black;height:1px;} \ No newline at end of file diff --git a/webroot/favicon.ico b/webroot/favicon.ico new file mode 100644 index 0000000..b36e81f Binary files /dev/null and b/webroot/favicon.ico differ diff --git a/webroot/img/cake.icon.gif b/webroot/img/cake.icon.gif new file mode 100644 index 0000000..f29f72e Binary files /dev/null and b/webroot/img/cake.icon.gif differ diff --git a/webroot/img/cake.power.gif b/webroot/img/cake.power.gif new file mode 100644 index 0000000..8f8d570 Binary files /dev/null and b/webroot/img/cake.power.gif differ diff --git a/webroot/index.php b/webroot/index.php new file mode 100644 index 0000000..9c2e5a8 --- /dev/null +++ b/webroot/index.php @@ -0,0 +1,93 @@ +dispatch($url); + } + if (Configure::read() > 0) { + echo ""; + } +?> \ No newline at end of file diff --git a/webroot/js/jqGrid/grid.locale-en.js b/webroot/js/jqGrid/grid.locale-en.js new file mode 100644 index 0000000..c23b241 --- /dev/null +++ b/webroot/js/jqGrid/grid.locale-en.js @@ -0,0 +1 @@ +(function(a){a.jgrid={defaults:{recordtext:"View {0} - {1} of {2}",emptyrecords:"No records to view",loadtext:"Loading...",pgtext:"Page {0} of {1}"},search:{caption:"Search...",Find:"Find",Reset:"Reset",odata:["equal","not equal","less","less or equal","greater","greater or equal","begins with","does not begin with","is in","is not in","ends with","does not end with","contains","does not contain"],groupOps:[{op:"AND",text:"all"},{op:"OR",text:"any"}],matchText:" match",rulesText:" rules"},edit:{addCaption:"Add Record",editCaption:"Edit Record",bSubmit:"Submit",bCancel:"Cancel",bClose:"Close",saveData:"Data has been changed! Save changes?",bYes:"Yes",bNo:"No",bExit:"Cancel",msg:{required:"Field is required",number:"Please, enter valid number",minValue:"value must be greater than or equal to ",maxValue:"value must be less than or equal to",email:"is not a valid e-mail",integer:"Please, enter valid integer value",date:"Please, enter valid date value",url:"is not a valid URL. Prefix required ('http://' or 'https://')"}},view:{caption:"View Record",bClose:"Close"},del:{caption:"Delete",msg:"Delete selected record(s)?",bSubmit:"Delete",bCancel:"Cancel"},nav:{edittext:"",edittitle:"Edit selected row",addtext:"",addtitle:"Add new row",deltext:"",deltitle:"Delete selected row",searchtext:"",searchtitle:"Find records",refreshtext:"",refreshtitle:"Reload Grid",alertcap:"Warning",alerttext:"Please, select row",viewtext:"",viewtitle:"View selected row"},col:{caption:"Show/Hide Columns",bSubmit:"Submit",bCancel:"Cancel"},errors:{errcap:"Error",nourl:"No url is set",norecords:"No records to process",model:"Length of colNames <> colModel!"},formatter:{integer:{thousandsSeparator:" ",defaultValue:"0"},number:{decimalSeparator:".",thousandsSeparator:" ",decimalPlaces:2,defaultValue:"0.00"},currency:{decimalSeparator:".",thousandsSeparator:" ",decimalPlaces:2,prefix:"",suffix:"",defaultValue:"0.00"},date:{dayNames:["Sun","Mon","Tue","Wed","Thr","Fri","Sat","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec","January","February","March","April","May","June","July","August","September","October","November","December"],AmPm:["am","pm","AM","PM"],S:function(b){return b<11||b>13?["st","nd","rd","th"][Math.min((b-1)%10,3)]:"th"},srcformat:"Y-m-d",newformat:"d/m/Y",masks:{ISO8601Long:"Y-m-d H:i:s",ISO8601Short:"Y-m-d",ShortDate:"n/j/Y",LongDate:"l, F d, Y",FullDateTime:"l, F d, Y g:i:s A",MonthDay:"F d",ShortTime:"g:i A",LongTime:"g:i:s A",SortableDateTime:"Y-m-d\\TH:i:s",UniversalSortableDateTime:"Y-m-d H:i:sO",YearMonth:"F, Y"},reformatAfterEdit:false},baseLinkUrl:"",showAction:"",target:"",checkbox:{disabled:true},idName:"id"}}})(jQuery); \ No newline at end of file diff --git a/webroot/js/jqGrid/jquery.jqGrid.min.js b/webroot/js/jqGrid/jquery.jqGrid.min.js new file mode 100644 index 0000000..d392c87 --- /dev/null +++ b/webroot/js/jqGrid/jquery.jqGrid.min.js @@ -0,0 +1,10 @@ +/* +* jqGrid 3.5.2 - jQuery Grid +* Copyright (c) 2008, Tony Tomov, tony@trirand.com +* Dual licensed under the MIT and GPL licenses +* http://www.opensource.org/licenses/mit-license.php +* http://www.gnu.org/licenses/gpl.html +* Date:2009-08-14 +* Modules: grid.base.js; jquery.fmatter.js; grid.custom.js; grid.common.js; grid.formedit.js; jquery.searchFilter.js; grid.setcolumns.js; grid.postext.js; +*/ +(function($){$.extend($.jgrid,{htmlDecode:function(value){if(value==" "||value==" "||(value.length==1&&value.charCodeAt(0)==160)){return""}return !value?value:String(value).replace(/&/g,"&").replace(/>/g,">").replace(/</g,"<").replace(/"/g,'"')},htmlEncode:function(value){return !value?value:String(value).replace(/&/g,"&").replace(/>/g,">").replace(/7){return rInd}for(i=0;irInd){return i}}return i},stripHtml:function(v){var regexp=/<("[^"]*"|'[^']*'|[^'">])*>/gi;if(v){return v.replace(regexp,"")}else{return v}},stringToDoc:function(xmlString){var xmlDoc;if(typeof xmlString!=="string"){return xmlString}try{var parser=new DOMParser();xmlDoc=parser.parseFromString(xmlString,"text/xml")}catch(e){xmlDoc=new ActiveXObject("Microsoft.XMLDOM");xmlDoc.async=false;xmlDoc.loadXML(xmlString)}return(xmlDoc&&xmlDoc.documentElement&&xmlDoc.documentElement.tagName!="parsererror")?xmlDoc:null},parse:function(jsonString){var js=jsonString;if(js.substr(0,9)=="while(1);"){js=js.substr(9)}if(js.substr(0,2)=="/*"){js=js.substr(2,js.length-4)}if(!js){js="{}"}with(window){return eval("("+js+")")}}});$.fn.jqGrid=function(p){p=$.extend(true,{url:"",height:150,page:1,rowNum:20,records:0,pager:"",pgbuttons:true,pginput:true,colModel:[],rowList:[],colNames:[],sortorder:"asc",sortname:"",datatype:"xml",mtype:"GET",altRows:false,selarrrow:[],savedRow:[],shrinkToFit:true,xmlReader:{},jsonReader:{},subGrid:false,subGridModel:[],reccount:0,lastpage:0,lastsort:0,selrow:null,beforeSelectRow:null,onSelectRow:null,onSortCol:null,ondblClickRow:null,onRightClickRow:null,onPaging:null,onSelectAll:null,loadComplete:null,gridComplete:null,loadError:null,loadBeforeSend:null,afterInsertRow:null,beforeRequest:null,onHeaderClick:null,viewrecords:false,loadonce:false,multiselect:false,multikey:false,editurl:null,search:false,caption:"",hidegrid:true,hiddengrid:false,postData:{},userData:{},treeGrid:false,treeGridModel:"nested",treeReader:{},treeANode:-1,ExpandColumn:null,tree_root_level:0,prmNames:{page:"page",rows:"rows",sort:"sidx",order:"sord",search:"_search",nd:"nd"},forceFit:false,gridstate:"visible",cellEdit:false,cellsubmit:"remote",nv:0,loadui:"enable",toolbar:[false,""],scroll:false,multiboxonly:false,deselectAfterSort:true,scrollrows:false,autowidth:false,scrollOffset:18,cellLayout:5,subGridWidth:20,multiselectWidth:20,gridview:false,rownumWidth:25,rownumbers:false,pagerpos:"center",recordpos:"right",footerrow:false,userDataOnFooter:false,hoverrows:true,altclass:"ui-priority-secondary",viewsortcols:[false,"vertical",true],resizeclass:"",autoencode:false},$.jgrid.defaults,p||{});var grid={headers:[],cols:[],footers:[],dragStart:function(i,x,y){this.resizing={idx:i,startX:x.clientX,sOL:y[0]};this.hDiv.style.cursor="col-resize";this.curGbox=$("#rs_m"+p.id,"#gbox_"+p.id);this.curGbox.css({display:"block",left:y[0],top:y[1],height:y[2]});document.onselectstart=new Function("return false")},dragMove:function(x){if(this.resizing){var diff=x.clientX-this.resizing.startX,h=this.headers[this.resizing.idx],newWidth=h.width+diff,hn,nWn;if(newWidth>33){this.curGbox.css({left:this.resizing.sOL+diff});if(p.forceFit===true){hn=this.headers[this.resizing.idx+p.nv];nWn=hn.width-diff;if(nWn>33){h.newWidth=newWidth;hn.newWidth=nWn;this.newWidth=p.tblwidth}}else{this.newWidth=p.tblwidth+diff;h.newWidth=newWidth}}}},dragEnd:function(){this.hDiv.style.cursor="default";if(this.resizing){var idx=this.resizing.idx,nw=this.headers[idx].newWidth||this.headers[idx].width;this.resizing=false;$("#rs_m"+p.id).css("display","none");p.colModel[idx].width=nw;this.headers[idx].width=nw;this.headers[idx].el.style.width=nw+"px";if(this.cols.length>0){this.cols[idx].style.width=nw+"px"}if(this.footers.length>0){this.footers[idx].style.width=nw+"px"}if(p.forceFit===true){nw=this.headers[idx+p.nv].newWidth||this.headers[idx+p.nv].width;this.headers[idx+p.nv].width=nw;this.headers[idx+p.nv].el.style.width=nw+"px";if(this.cols.length>0){this.cols[idx+p.nv].style.width=nw+"px"}if(this.footers.length>0){this.footers[idx+p.nv].style.width=nw+"px"}p.colModel[idx+p.nv].width=nw}else{p.tblwidth=this.newWidth;$("table:first",this.bDiv).css("width",p.tblwidth+"px");$("table:first",this.hDiv).css("width",p.tblwidth+"px");this.hDiv.scrollLeft=this.bDiv.scrollLeft;if(p.footerrow){$("table:first",this.sDiv).css("width",p.tblwidth+"px");this.sDiv.scrollLeft=this.bDiv.scrollLeft}}}this.curGbox=null;document.onselectstart=new Function("return true")},scrollGrid:function(){if(p.scroll===true){var scrollTop=this.bDiv.scrollTop;if(scrollTop!=this.scrollTop){this.scrollTop=scrollTop;if((this.bDiv.scrollHeight-scrollTop-$(this.bDiv).height())<=0){if(parseInt(p.page,10)+1<=parseInt(p.lastpage,10)){p.page=parseInt(p.page,10)+1;this.populate()}}}}this.hDiv.scrollLeft=this.bDiv.scrollLeft;if(p.footerrow){this.sDiv.scrollLeft=this.bDiv.scrollLeft}}};return this.each(function(){if(this.grid){return}this.p=p;var i;if(this.p.colNames.length===0){for(i=0;i"),ii,isMSIE=$.browser.msie?true:false,isSafari=$.browser.safari?true:false;$(gv).insertBefore(this);$(this).appendTo(gv).removeClass("scroll");var eg=$("
");$(eg).insertBefore(gv).attr("id","gbox_"+this.id);$(gv).appendTo(eg).attr("id","gview_"+this.id);if(isMSIE&&$.browser.version<=6){ii=''}else{ii=""}$("
").append(ii).insertBefore(gv);$("
"+this.p.loadtext+"
").insertBefore(gv);$(this).attr({cellSpacing:"0",cellPadding:"0",border:"0",role:"grid","aria-multiselectable":this.p.multiselect,"aria-labelledby":"gbox_"+this.id});var ts=this,bSR=$.isFunction(this.p.beforeSelectRow)?this.p.beforeSelectRow:false,ondblClickRow=$.isFunction(this.p.ondblClickRow)?this.p.ondblClickRow:false,onSortCol=$.isFunction(this.p.onSortCol)?this.p.onSortCol:false,loadComplete=$.isFunction(this.p.loadComplete)?this.p.loadComplete:false,loadError=$.isFunction(this.p.loadError)?this.p.loadError:false,loadBeforeSend=$.isFunction(this.p.loadBeforeSend)?this.p.loadBeforeSend:false,onRightClickRow=$.isFunction(this.p.onRightClickRow)?this.p.onRightClickRow:false,afterInsRow=$.isFunction(this.p.afterInsertRow)?this.p.afterInsertRow:false,onHdCl=$.isFunction(this.p.onHeaderClick)?this.p.onHeaderClick:false,beReq=$.isFunction(this.p.beforeRequest)?this.p.beforeRequest:false,onSC=$.isFunction(this.p.onCellSelect)?this.p.onCellSelect:false,sortkeys=["shiftKey","altKey","ctrlKey"],IntNum=function(val,defval){val=parseInt(val,10);if(isNaN(val)){return defval?defval:0}else{return val}},formatCol=function(pos,rowInd){var ral=ts.p.colModel[pos].align,result='style="';if(ral){result+="text-align:"+ral+";"}if(ts.p.colModel[pos].hidden===true){result+="display:none;"}if(rowInd===0){result+="width: "+grid.headers[pos].width+"px;"}return result+'"'},addCell=function(rowId,cell,pos,irow,srvr){var v,prp;v=formatter(rowId,cell,pos,srvr,"add");prp=formatCol(pos,irow);return''+v+""},formatter=function(rowId,cellval,colpos,rwdat,_act){var cm=ts.p.colModel[colpos],v;if(typeof cm.formatter!=="undefined"){var opts={rowId:rowId,colModel:cm};if($.isFunction(cm.formatter)){v=cm.formatter(cellval,opts,rwdat,_act)}else{if($.fmatter){v=$.fn.fmatter(cm.formatter,cellval,opts,rwdat,_act)}else{v=cellVal(cellval)}}}else{v=cellVal(cellval)}return ts.p.autoencode?$.jgrid.htmlEncode(v):v},cellVal=function(val){return val===undefined||val===null||val===""?" ":val+""},addMulti=function(rowid,pos,irow){var v='',prp=formatCol(pos,irow);return""+v+""},addRowNum=function(pos,irow,pG,rN){var v=(parseInt(pG)-1)*parseInt(rN)+1+irow,prp=formatCol(pos,irow);return'"+v+""},reader=function(datatype){var field,f=[],j=0,i;for(i=0;i0?rcnt:0}}else{return}var i,fpos,ir=0,v,row,gi=0,si=0,ni=0,idn,getId,f=[],rd={},rl=ts.rows.length,xmlr,rid,rowData=[],ari=0,cn=(ts.p.altRows===true)?ts.p.altclass:"",cn1;if(!ts.p.xmlReader.repeatitems){f=reader("xml")}if(ts.p.keyIndex===false){idn=ts.p.xmlReader.id;if(idn.indexOf("[")===-1){getId=function(trow,k){return $(idn,trow).text()||k}}else{getId=function(trow,k){return trow.getAttribute(idn.replace(/[\[\]]/g,""))||k}}}else{getId=function(trow){return(f.length-1>=ts.p.keyIndex)?$(f[ts.p.keyIndex],trow).text():$(ts.p.xmlReader.cell+":eq("+ts.p.keyIndex+")",trow).text()}}$(ts.p.xmlReader.page,xml).each(function(){ts.p.page=this.textContent||this.text||1});$(ts.p.xmlReader.total,xml).each(function(){ts.p.lastpage=this.textContent||this.text||1});$(ts.p.xmlReader.records,xml).each(function(){ts.p.records=this.textContent||this.text||0});$(ts.p.xmlReader.userdata,xml).each(function(){ts.p.userData[this.getAttribute("name")]=this.textContent||this.text});var gxml=$(ts.p.xmlReader.root+" "+ts.p.xmlReader.row,xml),gl=gxml.length,j=0;if(gxml&&gl){var rn=ts.p.rowNum;while(j';if(ts.p.rownumbers===true){rowData[ari++]=addRowNum(0,j,ts.p.page,ts.p.rowNum);ni=1}if(ts.p.multiselect===true){rowData[ari++]=addMulti(rid,ni,j);gi=1}if(ts.p.subGrid===true){rowData[ari++]=$(ts).addSubGridCell(gi+ni,j+rcnt);si=1}if(ts.p.xmlReader.repeatitems===true){$(ts.p.xmlReader.cell,xmlr).each(function(k){v=this.textContent||this.text;rd[ts.p.colModel[k+gi+si+ni].name]=v;rowData[ari++]=addCell(rid,v,k+gi+si+ni,j+rcnt,xmlr)})}else{for(i=0;i";if(ts.p.gridview===false){if(ts.p.treeGrid===true){fpos=ts.p.treeANode>=-1?ts.p.treeANode:0;row=$(rowData.join(""))[0];try{$(ts).setTreeNode(rd,row)}catch(e){}rl===0?$("tbody:first",t).append(row):$(ts.rows[j+fpos+rcnt]).after(row)}else{$("tbody:first",t).append(rowData.join(""))}if(ts.p.subGrid===true){try{$(ts).addSubGrid(ts.rows[ts.rows.length-1],gi+ni)}catch(e){}}if(afterInsRow){ts.p.afterInsertRow(rid,rd,xmlr)}rowData=[];ari=0}rd={};ir++;j++;if(rn!=-1&&ir>rn){break}}}if(ts.p.gridview===true){$("table:first",t).append(rowData.join(""))}ts.p.totaltime=new Date()-startReq;if(ir>0){ts.grid.cols=ts.rows[0].cells;if(ts.p.records===0){ts.p.records=gl}}rowData=null;if(!ts.p.treeGrid&&!ts.p.scroll){ts.grid.bDiv.scrollTop=0;ts.p.reccount=ir}ts.p.treeANode=-1;if(ts.p.userDataOnFooter){$(ts).footerData("set",ts.p.userData,true)}updatepager(false)},addJSONData=function(data,t,rcnt){var startReq=new Date();ts.p.reccount=0;if(data){if(ts.p.treeANode===-1&&ts.p.scroll===false){$("tbody",t).empty();rcnt=0}else{rcnt=rcnt>0?rcnt:0}}else{return}var ir=0,v,i,j,row,f=[],cur,gi=0,si=0,ni=0,len,drows,idn,rd={},fpos,rl=ts.rows.length,idr,rowData=[],ari=0,cn=(ts.p.altRows===true)?ts.p.altclass:"",cn1;ts.p.page=data[ts.p.jsonReader.page]||1;ts.p.lastpage=data[ts.p.jsonReader.total]||1;ts.p.records=data[ts.p.jsonReader.records]||0;ts.p.userData=data[ts.p.jsonReader.userdata]||{};if(!ts.p.jsonReader.repeatitems){f=reader("json")}if(ts.p.keyIndex===false){idn=ts.p.jsonReader.id;if(f.length>0&&!isNaN(idn)){idn=f[idn]}}else{idn=f.length>0?f[ts.p.keyIndex]:ts.p.keyIndex}drows=data[ts.p.jsonReader.root];if(drows){len=drows.length,i=0;var rn=ts.p.rowNum;while(i';if(ts.p.rownumbers===true){rowData[ari++]=addRowNum(0,i,ts.p.page,ts.p.rowNum);ni=1}if(ts.p.multiselect){rowData[ari++]=addMulti(idr,ni,i);gi=1}if(ts.p.subGrid){rowData[ari++]=$(ts).addSubGridCell(gi+ni,i+rcnt);si=1}if(ts.p.jsonReader.repeatitems===true){if(ts.p.jsonReader.cell){cur=cur[ts.p.jsonReader.cell]}for(j=0;j";if(ts.p.gridview===false){if(ts.p.treeGrid===true){fpos=ts.p.treeANode>=-1?ts.p.treeANode:0;row=$(rowData.join(""))[0];try{$(ts).setTreeNode(rd,row)}catch(e){}rl===0?$("tbody:first",t).append(row):$(ts.rows[i+fpos+rcnt]).after(row)}else{$("tbody:first",t).append(rowData.join(""))}if(ts.p.subGrid===true){try{$(ts).addSubGrid(ts.rows[ts.rows.length-1],gi+ni)}catch(e){}}if(afterInsRow){ts.p.afterInsertRow(idr,rd,cur)}rowData=[];ari=0}rd={};ir++;i++;if(rn!=-1&&ir>rn){break}}if(ts.p.gridview===true){$("table:first",t).append(rowData.join(""))}ts.p.totaltime=new Date()-startReq;if(ir>0){ts.grid.cols=ts.rows[0].cells;if(ts.p.records===0){ts.p.records=len}}}if(!ts.p.treeGrid&&!ts.p.scroll){ts.grid.bDiv.scrollTop=0}ts.p.reccount=ir;ts.p.treeANode=-1;if(ts.p.userDataOnFooter){$(ts).footerData("set",ts.p.userData,true)}updatepager(false)},updatepager=function(rn){var cp,last,base,bs,from,to,tot,fmt;base=(parseInt(ts.p.page)-1)*parseInt(ts.p.rowNum);if(ts.p.pager){if(ts.p.loadonce){cp=last=1;ts.p.lastpage=ts.page=1;$(".selbox",ts.p.pager).attr("disabled",true)}else{cp=IntNum(ts.p.page);last=IntNum(ts.p.lastpage);$(".selbox",ts.p.pager).attr("disabled",false)}if(ts.p.pginput===true){$(".ui-pg-input",ts.p.pager).val(ts.p.page);$("#sp_1",ts.p.pager).html(ts.p.lastpage)}if(ts.p.viewrecords){bs=ts.p.scroll===true?0:base;if(ts.p.reccount===0){$(".ui-paging-info",ts.p.pager).html(ts.p.emptyrecords)}else{from=bs+1;to=base+ts.p.reccount;tot=ts.p.records;if($.fmatter){fmt=$.jgrid.formatter.integer||{};from=$.fmatter.util.NumberFormat(from,fmt);to=$.fmatter.util.NumberFormat(to,fmt);tot=$.fmatter.util.NumberFormat(tot,fmt)}$(".ui-paging-info",ts.p.pager).html($.jgrid.format(ts.p.recordtext,from,to,tot))}}if(ts.p.pgbuttons===true){if(cp<=0){cp=last=1}if(cp==1){$("#first, #prev",ts.p.pager).addClass("ui-state-disabled").removeClass("ui-state-hover")}else{$("#first, #prev",ts.p.pager).removeClass("ui-state-disabled")}if(cp==last){$("#next, #last",ts.p.pager).addClass("ui-state-disabled").removeClass("ui-state-hover")}else{$("#next, #last",ts.p.pager).removeClass("ui-state-disabled")}}}if(rn===true&&ts.p.rownumbers===true){$("td.jqgrid-rownum",ts.rows).each(function(i){$(this).html(base+1+i)})}if($.isFunction(ts.p.gridComplete)){ts.p.gridComplete()}},populate=function(){if(!ts.grid.hDiv.loading){var prm={},dt,dstr;prm[ts.p.prmNames.search]=ts.p.search;prm[ts.p.prmNames.nd]=new Date().getTime();prm[ts.p.prmNames.rows]=ts.p.rowNum;prm[ts.p.prmNames.page]=ts.p.page;prm[ts.p.prmNames.sort]=ts.p.sortname;prm[ts.p.prmNames.order]=ts.p.sortorder;$.extend(ts.p.postData,prm);var rcnt=ts.p.scroll===false?0:ts.rows.length-1;if($.isFunction(ts.p.datatype)){ts.p.datatype(ts.p.postData,"load_"+ts.p.id);return}else{if(beReq){ts.p.beforeRequest()}}dt=ts.p.datatype.toLowerCase();switch(dt){case"json":case"xml":case"script":$.ajax({url:ts.p.url,type:ts.p.mtype,dataType:dt,data:ts.p.postData,complete:function(req,st){if(st=="success"||(req.statusText=="OK"&&req.status=="200")){if(dt==="json"||dt==="script"){addJSONData($.jgrid.parse(req.responseText),ts.grid.bDiv,rcnt)}if(dt==="xml"){addXmlData(req.responseXML,ts.grid.bDiv,rcnt)}if(loadComplete){loadComplete(req)}}req=null;endReq()},error:function(xhr,st,err){if(loadError){loadError(xhr,st,err)}endReq();xhr=null},beforeSend:function(xhr){beginReq();if(loadBeforeSend){loadBeforeSend(xhr)}}});if(ts.p.loadonce||ts.p.treeGrid){ts.p.datatype="local"}break;case"xmlstring":beginReq();addXmlData(dstr=$.jgrid.stringToDoc(ts.p.datastr),ts.grid.bDiv);ts.p.datatype="local";if(loadComplete){loadComplete(dstr)}ts.p.datastr=null;endReq();break;case"jsonstring":beginReq();if(typeof ts.p.datastr=="string"){dstr=$.jgrid.parse(ts.p.datastr)}else{dstr=ts.p.datastr}addJSONData(dstr,ts.grid.bDiv);ts.p.datatype="local";if(loadComplete){loadComplete(dstr)}ts.p.datastr=null;endReq();break;case"local":case"clientside":beginReq();ts.p.datatype="local";sortArrayData();endReq();break}}},beginReq=function(){ts.grid.hDiv.loading=true;if(ts.p.hiddengrid){return}switch(ts.p.loadui){case"disable":break;case"enable":$("#load_"+ts.p.id).show();break;case"block":$("#lui_"+ts.p.id).show();$("#load_"+ts.p.id).show();break}},endReq=function(){ts.grid.hDiv.loading=false;switch(ts.p.loadui){case"disable":break;case"enable":$("#load_"+ts.p.id).hide();break;case"block":$("#lui_"+ts.p.id).hide();$("#load_"+ts.p.id).hide();break}},sortArrayData=function(){var stripNum=/[\$,%]/g;var rows=[],col=0,st,sv,findSortKey,newDir=(ts.p.sortorder=="asc")?1:-1;$.each(ts.p.colModel,function(i,v){if(this.index==ts.p.sortname||this.name==ts.p.sortname){col=ts.p.lastsort=i;st=this.sorttype;return false}});if(st=="float"||st=="number"||st=="currency"){findSortKey=function($cell){var key=parseFloat($cell.replace(stripNum,""));return isNaN(key)?0:key}}else{if(st=="int"||st=="integer"){findSortKey=function($cell){return IntNum($cell.replace(stripNum,""))}}else{if(st=="date"){findSortKey=function($cell){var fd=ts.p.colModel[col].datefmt||"Y-m-d";return parseDate(fd,$cell).getTime()}}else{findSortKey=function($cell){return $.trim($cell.toUpperCase())}}}}$.each(ts.rows,function(index,row){try{sv=$.unformat($(row).children("td").eq(col),{colModel:ts.p.colModel[col]},col,true)}catch(_){sv=$(row).children("td").eq(col).text()}row.sortKey=findSortKey(sv);rows[index]=this});if(ts.p.treeGrid){$(ts).SortTree(newDir)}else{rows.sort(function(a,b){if(a.sortKeyb.sortKey){return newDir}return 0});if(rows[0]){$("td",rows[0]).each(function(k){$(this).css("width",grid.headers[k].width+"px")});grid.cols=rows[0].cells}$.each(rows,function(index,row){$("tbody",ts.grid.bDiv).append(row);row.sortKey=null})}if(ts.p.multiselect){$("tbody tr",ts.grid.bDiv).removeClass("ui-state-highlight");$("[id^=jqg_]",ts.rows).attr("checked",false);$("#cb_jqg",ts.grid.hDiv).attr("checked",false);ts.p.selarrrow=[]}ts.grid.bDiv.scrollTop=0},parseDate=function(format,date){var tsp={m:1,d:1,y:1970,h:0,i:0,s:0},k,hl,dM;date=date.split(/[\\\/:_;.\t\T\s-]/);format=format.split(/[\\\/:_;.\t\T\s-]/);var dfmt=$.jgrid.formatter.date.monthNames;for(k=0,hl=format.length;k11){date[k]=dM+1-12}}tsp[format[k].toLowerCase()]=parseInt(date[k],10)}tsp.m=parseInt(tsp.m,10)-1;var ty=tsp.y;if(ty>=70&&ty<=99){tsp.y=1900+tsp.y}else{if(ty>=0&&ty<=69){tsp.y=2000+tsp.y}}return new Date(tsp.y,tsp.m,tsp.d,tsp.h,tsp.i,tsp.s,0)},setPager=function(){var sep="",pgid=$(ts.p.pager).attr("id")||"pager",pginp=(ts.p.pginput===true)?""+$.jgrid.format(ts.p.pgtext||"","","")+"":"",pgl="",str,pgcnt,lft,cent,rgt,twd,tdw,i,clearVals=function(onpaging){if($.isFunction(ts.p.onPaging)){ts.p.onPaging(onpaging)}ts.p.selrow=null;if(ts.p.multiselect){ts.p.selarrrow=[];$("#cb_jqg",ts.grid.hDiv).attr("checked",false)}ts.p.savedRow=[]};pgcnt="pg_"+pgid;lft=pgid+"_left";cent=pgid+"_center";rgt=pgid+"_right";$(ts.p.pager).addClass("ui-jqgrid-pager corner-bottom").append("
");if(ts.p.pgbuttons===true){pgl+="";pgl+="";pgl+=pginp!=""?sep+pginp+sep:"";pgl+="";pgl+=""}else{if(pginp!=""){pgl+=pginp}}if(ts.p.rowList.length>0){str="";pgl+=""+str+""}pgl+="";if(ts.p.viewrecords===true){$("td#"+pgid+"_"+ts.p.recordpos,"#"+pgcnt).append("
")}$("td#"+pgid+"_"+ts.p.pagerpos,"#"+pgcnt).append(pgl);tdw=$(".ui-jqgrid").css("font-size")||"11px";$("body").append("");twd=$(pgl).clone().appendTo("#testpg").width();setTimeout(function(){$("#testpg").remove()},0);if(twd>0){twd+=25;$("td#"+pgid+"_"+ts.p.pagerpos,"#"+pgcnt).width(twd)}ts.p._nvtd=[];ts.p._nvtd[0]=twd?Math.floor((ts.p.width-twd)/2):Math.floor(ts.p.width/3);ts.p._nvtd[1]=0;pgl=null;$(".ui-pg-selbox","#"+pgcnt).bind("change",function(){ts.p.rowNum=this.value;clearVals("records");populate();return false});if(ts.p.pgbuttons===true){$(".ui-pg-button","#"+pgcnt).hover(function(e){if($(this).hasClass("ui-state-disabled")){this.style.cursor="default"}else{$(this).addClass("ui-state-hover");this.style.cursor="pointer"}},function(e){if($(this).hasClass("ui-state-disabled")){}else{$(this).removeClass("ui-state-hover");this.style.cursor="default"}});$("#first, #prev, #next, #last",ts.p.pager).click(function(e){var cp=IntNum(ts.p.page),last=IntNum(ts.p.lastpage),selclick=false,fp=true,pp=true,np=true,lp=true;if(last===0||last===1){fp=false;pp=false;np=false;lp=false}else{if(last>1&&cp>=1){if(cp===1){fp=false;pp=false}else{if(cp>1&&cp1&&cp===0){np=false;lp=false;cp=last-1}}}if(this.id==="first"&&fp){ts.p.page=1;selclick=true}if(this.id==="prev"&&pp){ts.p.page=(cp-1);selclick=true}if(this.id==="next"&&np){ts.p.page=(cp+1);selclick=true}if(this.id==="last"&&lp){ts.p.page=last;selclick=true}if(selclick){clearVals(this.id);populate()}return false})}if(ts.p.pginput===true){$("input.ui-pg-input","#"+pgcnt).keypress(function(e){var key=e.charCode?e.charCode:e.keyCode?e.keyCode:0;if(key==13){ts.p.page=($(this).val()>0)?$(this).val():ts.p.page;clearVals("user");populate();return false}return this})}},sortData=function(index,idxcol,reload,sor){if(!ts.p.colModel[idxcol].sortable){return}var imgs,so;if(ts.p.savedRow.length>0){return}if(!reload){if(ts.p.lastsort==idxcol){if(ts.p.sortorder=="asc"){ts.p.sortorder="desc"}else{if(ts.p.sortorder=="desc"){ts.p.sortorder="asc"}}}else{ts.p.sortorder="asc"}ts.p.page=1}if(sor){if(ts.p.lastsort==idxcol&&ts.p.sortorder==sor){return}else{ts.p.sortorder=sor}}var thd=$("thead:first",ts.grid.hDiv).get(0);$("tr th:eq("+ts.p.lastsort+") span.ui-grid-ico-sort",thd).addClass("ui-state-disabled");$("tr th:eq("+ts.p.lastsort+")",thd).attr("aria-selected","false");$("tr th:eq("+idxcol+") span.ui-icon-"+ts.p.sortorder,thd).removeClass("ui-state-disabled");$("tr th:eq("+idxcol+")",thd).attr("aria-selected","true");if(!ts.p.viewsortcols[0]){if(ts.p.lastsort!=idxcol){$("tr th:eq("+ts.p.lastsort+") span.s-ico",thd).hide();$("tr th:eq("+idxcol+") span.s-ico",thd).show()}}ts.p.lastsort=idxcol;index=index.substring(5);ts.p.sortname=ts.p.colModel[idxcol].index||index;so=ts.p.sortorder;if(onSortCol){onSortCol(index,idxcol,so)}if(ts.p.datatype=="local"){if(ts.p.deselectAfterSort){$(ts).resetSelection()}}else{ts.p.selrow=null;if(ts.p.multiselect){$("#cb_jqg",ts.grid.hDiv).attr("checked",false)}ts.p.selarrrow=[];ts.p.savedRow=[]}if(ts.p.scroll===true){$("tbody tr",ts.grid.bDiv).remove()}if(ts.p.subGrid&&ts.p.datatype=="local"){$("td.sgexpanded","#"+ts.p.id).each(function(){$(this).trigger("click")})}populate();if(ts.p.sortname!=index&&idxcol){ts.p.lastsort=idxcol}},setColWidth=function(){var initwidth=0,brd=ts.p.cellLayout,vc=0,lvc,scw=ts.p.scrollOffset,cw,hs=false,aw,tw=0,gw=0,msw=ts.p.multiselectWidth,sgw=ts.p.subGridWidth,rnw=ts.p.rownumWidth,cl=ts.p.cellLayout,cr;$.each(ts.p.colModel,function(i){if(typeof this.hidden==="undefined"){this.hidden=false}if(this.hidden===false){initwidth+=IntNum(this.width);vc++}});if(isNaN(ts.p.width)){ts.p.width=grid.width=initwidth}else{grid.width=ts.p.width}ts.p.tblwidth=initwidth;if(ts.p.shrinkToFit===false&&ts.p.forceFit===true){ts.p.forceFit=false}if(ts.p.shrinkToFit===true){if(isSafari){brd=0;msw+=cl;sgw+=cl;rnw+=cl}if(ts.p.multiselect){tw=msw;gw=msw+brd;vc--}if(ts.p.subGrid){tw+=sgw;gw+=sgw+brd;vc--}if(ts.p.rownumbers){tw+=rnw;gw+=rnw+brd;vc--}aw=grid.width-brd*vc-gw;if(isNaN(ts.p.height)){}else{aw-=scw;hs=true}initwidth=0;$.each(ts.p.colModel,function(i){if(this.hidden===false&&this.name!=="cb"&&this.name!=="subgrid"&&this.name!=="rn"){cw=Math.floor(aw/(ts.p.tblwidth-tw)*this.width);this.width=cw;initwidth+=cw;lvc=i}});cr=0;if(hs&&grid.width-gw-(initwidth+brd*vc)!==scw){cr=grid.width-gw-(initwidth+brd*vc)-scw}else{if(!hs&&Math.abs(grid.width-gw-(initwidth+brd*vc))!==1){cr=grid.width-gw-(initwidth+brd*vc)}}ts.p.colModel[lvc].width+=cr;ts.p.tblwidth=initwidth+tw+cr}},nextVisible=function(iCol){var ret=iCol,j=iCol,i;for(i=iCol+1;i");this.p.colModel.unshift({name:"cb",width:isSafari?ts.p.multiselectWidth+ts.p.cellLayout:ts.p.multiselectWidth,sortable:false,resizable:false,hidedlg:true,search:false,align:"center"})}if(this.p.rownumbers){this.p.colNames.unshift("");this.p.colModel.unshift({name:"rn",width:ts.p.rownumWidth,sortable:false,resizable:false,hidedlg:true,search:false,align:"center"})}ts.p.xmlReader=$.extend({root:"rows",row:"row",page:"rows>page",total:"rows>total",records:"rows>records",repeatitems:true,cell:"cell",id:"[id]",userdata:"userdata",subgrid:{root:"rows",row:"row",repeatitems:true,cell:"cell"}},ts.p.xmlReader);ts.p.jsonReader=$.extend({root:"rows",page:"page",total:"total",records:"records",repeatitems:true,cell:"cell",id:"id",userdata:"userdata",subgrid:{root:"rows",repeatitems:true,cell:"cell"}},ts.p.jsonReader);if(ts.p.scroll===true){ts.p.pgbuttons=false;ts.p.pginput=false;ts.p.rowList=[]}var thead="",tdc,idn,w,res,sort,td,ptr,tbody,imgs,iac="",idc="";if(ts.p.shrinkToFit===true&&ts.p.forceFit===true){for(i=ts.p.colModel.length-1;i>=0;i--){if(!ts.p.colModel[i].hidden){ts.p.colModel[i].resizable=false;break}}}if(ts.p.viewsortcols[1]=="horizontal"){iac=" ui-i-asc";idc=" ui-i-desc"}tdc=isMSIE?"class='ui-th-div-ie'":"";imgs="";for(i=0;i";idn=ts.p.colModel[i].index||ts.p.colModel[i].name;thead+="
"+ts.p.colNames[i];if(idn==ts.p.sortname){ts.p.lastsort=i}thead+=imgs+"
"}thead+="";$(this).append(thead);$("thead tr:first th",this).hover(function(){$(this).addClass("ui-state-hover")},function(){$(this).removeClass("ui-state-hover")});if(this.p.multiselect){var onSA=true,emp=[],chk;if(typeof ts.p.onSelectAll!=="function"){onSA=false}$("#cb_jqg",this).bind("click",function(){if(this.checked){$("[id^=jqg_]",ts.rows).attr("checked",true);$(ts.rows).each(function(i){if(!$(this).hasClass("subgrid")){$(this).addClass("ui-state-highlight").attr("aria-selected","true");ts.p.selarrrow[i]=ts.p.selrow=this.id}});chk=true;emp=[]}else{$("[id^=jqg_]",ts.rows).attr("checked",false);$(ts.rows).each(function(i){if(!$(this).hasClass("subgrid")){$(this).removeClass("ui-state-highlight").attr("aria-selected","false");emp[i]=this.id}});ts.p.selarrrow=[];ts.p.selrow=null;chk=false}if(onSA){ts.p.onSelectAll(chk?ts.p.selarrrow:emp,chk)}})}$.each(ts.p.colModel,function(i){if(!this.width){this.width=150}});if(ts.p.autowidth===true){var pw=$(eg).innerWidth();ts.p.width=pw>0?pw:"nw"}setColWidth();$(eg).css("width",grid.width+"px").append("
 
");$(gv).css("width",grid.width+"px");thead=$("thead:first",ts).get(0);var tfoot="";$("tr:first th",thead).each(function(j){var ht=$("div",this)[0];w=ts.p.colModel[j].width;if(typeof ts.p.colModel[j].resizable==="undefined"){ts.p.colModel[j].resizable=true}res=document.createElement("span");$(res).html(" ");if(ts.p.colModel[j].resizable){$(this).addClass(ts.p.resizeclass);$(res).mousedown(function(e){if(ts.p.forceFit===true){ts.p.nv=nextVisible(j)}grid.dragStart(j,e,getOffset(j));return false}).addClass("ui-jqgrid-resize")}else{res=""}$(this).css("width",w+"px").prepend(res);if(ts.p.colModel[j].hidden){$(this).css("display","none")}grid.headers[j]={width:w,el:this};sort=ts.p.colModel[j].sortable;if(typeof sort!=="boolean"){ts.p.colModel[j].sortable=true;sort=true}var nm=ts.p.colModel[j].name;if(!(nm=="cb"||nm=="subgrid"||nm=="rn")){if(ts.p.viewsortcols[2]==false){$(".ui-grid-ico-sort",this).click(function(){sortData(ht.id,j,true,$(this).attr("sort"));return false})}else{$("div",this).addClass("ui-jqgrid-sortable").click(function(){sortData(ht.id,j);return false})}}if(sort){if(ts.p.viewsortcols[0]){$("div span.s-ico",this).show();if(j==ts.p.lastsort){$("div span.ui-icon-"+ts.p.sortorder,this).removeClass("ui-state-disabled")}}else{if(j==ts.p.lastsort){$("div span.s-ico",this).show();$("div span.ui-icon-"+ts.p.sortorder,this).removeClass("ui-state-disabled")}}}tfoot+=""});tfoot+="
 
";tbody=document.createElement("tbody");this.appendChild(tbody);$(this).addClass("ui-jqgrid-btable");var hTable=$("
").append(thead),hg=(ts.p.caption&&ts.p.hiddengrid===true)?true:false,hb=$("
");grid.hDiv=document.createElement("div");$(grid.hDiv).css({width:grid.width+"px"}).addClass("ui-state-default ui-jqgrid-hdiv").append(hb).bind("selectstart",function(){return false});$(hb).append(hTable);if(hg){$(grid.hDiv).hide();ts.p.gridstate="hidden"}ts.p._height=0;if(ts.p.pager){if(typeof ts.p.pager=="string"){if(ts.p.pager.substr(0,1)!="#"){ts.p.pager="#"+ts.p.pager}}$(ts.p.pager).css({width:grid.width+"px"}).appendTo(eg).addClass("ui-state-default ui-jqgrid-pager");ts.p._height+=parseInt($(ts.p.pager).height(),10);if(hg){$(ts.p.pager).hide()}setPager()}if(ts.p.cellEdit===false&&ts.p.hoverrows===true){$(ts).bind("mouseover",function(e){ptr=$(e.target).parents("tr.jqgrow");if($(ptr).attr("class")!=="subgrid"){$(ptr).addClass("ui-state-hover")}return false}).bind("mouseout",function(e){ptr=$(e.target).parents("tr.jqgrow");$(ptr).removeClass("ui-state-hover");return false})}var ri,ci;$(ts).before(grid.hDiv).click(function(e){td=e.target;var scb=$(td).hasClass("cbox");ptr=$(td,ts.rows).parents("tr.jqgrow");if($(ptr).length===0){return false}var cSel=true;if(bSR){cSel=bSR(ptr[0].id)}if(td.tagName=="A"){return true}if(cSel===true){if(ts.p.cellEdit===true){if(ts.p.multiselect&&scb){$(ts).setSelection(false,true,ptr)}else{ri=ptr[0].rowIndex;ci=!$(td).is("td")?$(td).parents("td:first")[0].cellIndex:td.cellIndex;if(isMSIE){ci=$.jgrid.getAbsoluteIndex(ptr[0],ci)}try{$(ts).editCell(ri,ci,true)}catch(e){}}}else{if(!ts.p.multikey){if(ts.p.multiselect&&ts.p.multiboxonly){if(scb){$(ts).setSelection(false,true,ptr)}else{$(ts.p.selarrrow).each(function(i,n){var ind=ts.rows.namedItem(n);$(ind).removeClass("ui-state-highlight");$("#jqg_"+n.replace(".","\\."),ind).attr("checked",false)});ts.p.selarrrow=[];$("#cb_jqg",ts.grid.hDiv).attr("checked",false);$(ts).setSelection(false,true,ptr)}}else{$(ts).setSelection(false,true,ptr)}}else{if(e[ts.p.multikey]){$(ts).setSelection(false,true,ptr)}else{if(ts.p.multiselect&&scb){scb=$("[id^=jqg_]",ptr).attr("checked");$("[id^=jqg_]",ptr).attr("checked",!scb)}}}}if(onSC){ri=ptr[0].id;ci=!$(td).is("td")?$(td).parents("td:first")[0].cellIndex:td.cellIndex;if(isMSIE){ci=$.jgrid.getAbsoluteIndex(ptr[0],ci)}onSC(ri,ci,$(td).html(),td)}}e.stopPropagation()}).bind("reloadGrid",function(e){if(ts.p.treeGrid===true){ts.p.datatype=ts.p.treedatatype}if(ts.p.datatype=="local"){$(ts).resetSelection()}else{if(!ts.p.treeGrid){ts.p.selrow=null;if(ts.p.multiselect){ts.p.selarrrow=[];$("#cb_jqg",ts.grid.hDiv).attr("checked",false)}if(ts.p.cellEdit){ts.p.savedRow=[]}}}if(ts.p.scroll===true){$("tbody tr",ts.grid.bDiv).remove()}ts.grid.populate();return false});if(ondblClickRow){$(this).dblclick(function(e){td=(e.target);ptr=$(td,ts.rows).parents("tr.jqgrow");if($(ptr).length===0){return false}ri=ptr[0].rowIndex;ci=!$(td).is("td")?$(td).parents("td:first")[0].cellIndex:td.cellIndex;if(isMSIE){ci=$.jgrid.getAbsoluteIndex(ptr[0],ci)}ts.p.ondblClickRow($(ptr).attr("id"),ri,ci);return false})}if(onRightClickRow){$(this).bind("contextmenu",function(e){td=e.target;ptr=$(td,ts.rows).parents("tr.jqgrow");if($(ptr).length===0){return false}if(!ts.p.multiselect){$(ts).setSelection(false,true,ptr)}ri=ptr[0].rowIndex;ci=!$(td).is("td")?$(td).parents("td:first")[0].cellIndex:td.cellIndex;if(isMSIE){ci=$.jgrid.getAbsoluteIndex(ptr[0],ci)}ts.p.onRightClickRow($(ptr).attr("id"),ri,ci);return false})}grid.bDiv=document.createElement("div");$(grid.bDiv).append(this).addClass("ui-jqgrid-bdiv").css({height:ts.p.height+(isNaN(ts.p.height)?"":"px"),width:(grid.width)+"px"}).scroll(function(e){grid.scrollGrid()});$("table:first",grid.bDiv).css({width:ts.p.tblwidth+"px"});if(isMSIE){if($("tbody",this).size()===2){$("tbody:first",this).remove()}if(ts.p.multikey){$(grid.bDiv).bind("selectstart",function(){return false})}}else{if(ts.p.multikey){$(grid.bDiv).bind("mousedown",function(){return false})}}if(hg){$(grid.bDiv).hide()}grid.cDiv=document.createElement("div");var arf=ts.p.hidegrid===true?$("").addClass("ui-jqgrid-titlebar-close HeaderButton").hover(function(){arf.addClass("ui-state-hover")},function(){arf.removeClass("ui-state-hover")}).append(""):"";$(grid.cDiv).append(arf).append(""+ts.p.caption+"").addClass("ui-jqgrid-titlebar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix");$(grid.cDiv).insertBefore(grid.hDiv);if(ts.p.toolbar[0]){grid.uDiv=document.createElement("div");if(ts.p.toolbar[1]=="top"){$(grid.uDiv).insertBefore(grid.hDiv)}else{if(ts.p.toolbar[1]=="bottom"){$(grid.uDiv).insertAfter(grid.hDiv)}}if(ts.p.toolbar[1]=="both"){grid.ubDiv=document.createElement("div");$(grid.uDiv).insertBefore(grid.hDiv).addClass("ui-userdata ui-state-default").attr("id","t_"+this.id);$(grid.ubDiv).insertAfter(grid.hDiv).addClass("ui-userdata ui-state-default").attr("id","tb_"+this.id);ts.p._height+=IntNum($(grid.ubDiv).height());if(hg){$(grid.ubDiv).hide()}}else{$(grid.uDiv).width(grid.width).addClass("ui-userdata ui-state-default").attr("id","t_"+this.id)}ts.p._height+=IntNum($(grid.uDiv).height());if(hg){$(grid.uDiv).hide()}}if(ts.p.footerrow){grid.sDiv=document.createElement("div");hb=$("
");$(grid.sDiv).addClass("ui-jqgrid-sdiv").append(hb).insertAfter(grid.hDiv).width(grid.width);$(hb).append(tfoot);grid.footers=$(".ui-jqgrid-ftable",grid.sDiv)[0].rows[0].cells;if(ts.p.rownumbers){grid.footers[0].className="ui-state-default jqgrid-rownum"}if(hg){$(grid.sDiv).hide()}}if(ts.p.caption){ts.p._height+=parseInt($(grid.cDiv,ts).height(),10);var tdt=ts.p.datatype;if(ts.p.hidegrid===true){$(".ui-jqgrid-titlebar-close",grid.cDiv).toggle(function(){$(".ui-jqgrid-bdiv, .ui-jqgrid-hdiv","#gview_"+ts.p.id).slideUp("fast");if(ts.p.pager){$(ts.p.pager).slideUp("fast")}if(ts.p.toolbar[0]===true){if(ts.p.toolbar[1]=="both"){$(grid.ubDiv).slideUp("fast")}$(grid.uDiv).slideUp("fast")}if(ts.p.footerrow){$(".ui-jqgrid-sdiv","#gbox_"+ts.p.id).slideUp("fast")}$("span",this).removeClass("ui-icon-circle-triangle-n").addClass("ui-icon-circle-triangle-s");ts.p.gridstate="hidden";if(onHdCl){if(!hg){ts.p.onHeaderClick(ts.p.gridstate)}}},function(){$(".ui-jqgrid-hdiv, .ui-jqgrid-bdiv","#gview_"+ts.p.id).slideDown("fast");if(ts.p.pager){$(ts.p.pager).slideDown("fast")}if(ts.p.toolbar[0]===true){if(ts.p.toolbar[1]=="both"){$(grid.ubDiv).slideDown("fast")}$(grid.uDiv).slideDown("fast")}if(ts.p.footerrow){$(".ui-jqgrid-sdiv","#gbox_"+ts.p.id).slideDown("fast")}$("span",this).removeClass("ui-icon-circle-triangle-s").addClass("ui-icon-circle-triangle-n");if(hg){ts.p.datatype=tdt;populate();hg=false}ts.p.gridstate="visible";if(onHdCl){ts.p.onHeaderClick(ts.p.gridstate)}});if(hg){$(".ui-jqgrid-titlebar-close",grid.cDiv).trigger("click");ts.p.datatype="local"}}}else{$(grid.cDiv).hide()}$(grid.hDiv).after(grid.bDiv).mousemove(function(e){if(grid.resizing){grid.dragMove(e)}return false});ts.p._height+=parseInt($(grid.hDiv).height(),10);$(document).mouseup(function(e){if(grid.resizing){grid.dragEnd();return false}return true});this.updateColumns=function(){var r=this.rows[0],self=this;if(r){$("td",r).each(function(k){$(this).css("width",self.grid.headers[k].width+"px")});this.grid.cols=r.cells}return this};ts.formatCol=function(a,b){return formatCol(a,b)};ts.sortData=function(a,b,c){sortData(a,b,c)};ts.updatepager=function(a){updatepager(a)};ts.formatter=function(rowId,cellval,colpos,rwdat,act){return formatter(rowId,cellval,colpos,rwdat,act)};$.extend(grid,{populate:function(){populate()}});this.grid=grid;ts.addXmlData=function(d){addXmlData(d,ts.grid.bDiv)};ts.addJSONData=function(d){addJSONData(d,ts.grid.bDiv)};populate();ts.p.hiddengrid=false;$(window).unload(function(){$(this).empty();this.grid=null;this.p=null})})};$.fn.extend({getGridParam:function(pName){var $t=this[0];if(!$t.grid){return}if(!pName){return $t.p}else{return $t.p[pName]?$t.p[pName]:null}},setGridParam:function(newParams){return this.each(function(){if(this.grid&&typeof(newParams)==="object"){$.extend(true,this.p,newParams)}})},getDataIDs:function(){var ids=[],i=0,len;this.each(function(){len=this.rows.length;if(len&&len>0){while(i=0){if(ner>olr){scrGrid(ner,"d")}else{scrGrid(ner,"u")}}}if(!$t.p.multiselect){if($(pt).attr("class")!=="subgrid"){if($t.p.selrow){$("tr#"+$t.p.selrow.replace(".","\\."),$t.grid.bDiv).removeClass("ui-state-highlight").attr("aria-selected","false")}$t.p.selrow=selection;$(pt).addClass("ui-state-highlight").attr("aria-selected","true");if($t.p.onSelectRow&&onsr){$t.p.onSelectRow($t.p.selrow,true)}}}else{$t.p.selrow=selection;ia=$.inArray($t.p.selrow,$t.p.selarrrow);if(ia===-1){if($(pt).attr("class")!=="subgrid"){$(pt).addClass("ui-state-highlight").attr("aria-selected","true")}stat=true;$("#jqg_"+$t.p.selrow.replace(".","\\."),$t.rows).attr("checked",stat);$t.p.selarrrow.push($t.p.selrow);if($t.p.onSelectRow&&onsr){$t.p.onSelectRow($t.p.selrow,stat)}}else{if($(pt).attr("class")!=="subgrid"){$(pt).removeClass("ui-state-highlight").attr("aria-selected","false")}stat=false;$("#jqg_"+$t.p.selrow.replace(".","\\."),$t.rows).attr("checked",stat);$t.p.selarrrow.splice(ia,1);if($t.p.onSelectRow&&onsr){$t.p.onSelectRow($t.p.selrow,stat)}tpsr=$t.p.selarrrow[0];$t.p.selrow=(tpsr===undefined)?null:tpsr}}function scrGrid(iR,tp){var ch=$($t.grid.bDiv)[0].clientHeight,st=$($t.grid.bDiv)[0].scrollTop,nROT=$t.rows[iR].offsetTop+$t.rows[iR].clientHeight,pROT=$t.rows[iR].offsetTop;if(tp=="d"){if(nROT>=ch){$($t.grid.bDiv)[0].scrollTop=st+nROT-pROT}}if(tp=="u"){if(pROT span:first",ind).html(vl).attr("title",$.jgrid.stripHtml(vl))}else{$("td:eq("+i+")",ind).html(vl).attr("title",$.jgrid.stripHtml(vl))}success=true}})}});return success},addRowData:function(rowid,data,pos,src){if(!pos){pos="last"}var success=false,nm,row,gi=0,si=0,ni=0,sind,i,v,prp="";if(data){this.each(function(){var t=this;rowid=typeof(rowid)!="undefined"?rowid+"":t.p.records+1;row='';if(t.p.rownumbers===true){prp=t.formatCol(ni,1);row+='0";ni=1}if(t.p.multiselect){v='';prp=t.formatCol(ni,1);row+='"+v+"";gi=1}if(t.p.subGrid===true){row+=$(t).addSubGridCell(gi+ni,1);si=1}for(i=gi+si+ni;i'+v+""}row+="";if(t.p.subGrid===true){row=$(row)[0];$(t).addSubGrid(row,gi+ni)}if(t.rows.length===0){$("table:first",t.grid.bDiv).append(row)}else{switch(pos){case"last":$(t.rows[t.rows.length-1]).after(row);break;case"first":$(t.rows[0]).before(row);break;case"after":sind=t.rows.namedItem(src);sind!=null?$(t.rows[sind.rowIndex+1]).hasClass("ui-subgrid")?$(t.rows[sind.rowIndex+1]).after(row):$(sind).after(row):"";break;case"before":sind=t.rows.namedItem(src);sind!=null?$(sind).before(row):"";break}}t.p.records++;t.p.reccount++;if(pos==="first"||(pos==="before"&&sind===0)||t.rows.length===1){t.updateColumns()}if(t.p.altRows===true){var cn=t.p.altclass;if(pos=="last"){if(t.rows.length%2==1){$(t.rows[t.rows.length-1]).addClass(cn)}}else{$(t.rows).each(function(i){if(i%2==1){$(this).addClass(cn)}else{$(this).removeClass(cn)}})}}try{t.p.afterInsertRow(rowid,data)}catch(e){}t.updatepager(true);success=true})}return success},footerData:function(action,data,format){var nm,success=false,res={};function isEmpty(obj){for(var i in obj){return false}return true}if(typeof(action)=="undefined"){action="get"}if(typeof(format)!="boolean"){format=true}action=action.toLowerCase();this.each(function(){var t=this,vl,ind;if(!t.grid||!t.p.footerrow){return false}if(action=="set"){if(isEmpty(data)){return false}}success=true;$(this.p.colModel).each(function(i){nm=this.name;if(action=="set"){if(data[nm]!=undefined){vl=format?t.formatter("",data[nm],i,data,"edit"):data[nm];$("tr.footrow td:eq("+i+")",t.grid.sDiv).html(vl).attr("title",$.jgrid.stripHtml(vl));success=true}}else{if(action=="get"){res[nm]=$("tr.footrow td:eq("+i+")",t.grid.sDiv).html()}}})});return action=="get"?res:success},ShowHideCol:function(colname,show){return this.each(function(){var $t=this,fndh=false;if(!$t.grid){return}if(typeof colname==="string"){colname=[colname]}show=show!="none"?"":"none";var sw=show==""?true:false;$(this.p.colModel).each(function(i){if($.inArray(this.name,colname)!==-1&&this.hidden===sw){$("tr",$t.grid.hDiv).each(function(){$("th:eq("+i+")",this).css("display",show)});$($t.rows).each(function(j){$("td:eq("+i+")",$t.rows[j]).css("display",show)});if($t.p.footerrow){$("td:eq("+i+")",$t.grid.sDiv).css("display",show)}if(show=="none"){$t.p.tblwidth-=this.width}else{$t.p.tblwidth+=this.width}this.hidden=!sw;fndh=true}});if(fndh===true){$("table:first",$t.grid.hDiv).width($t.p.tblwidth);$("table:first",$t.grid.bDiv).width($t.p.tblwidth);$t.grid.hDiv.scrollLeft=$t.grid.bDiv.scrollLeft;if($t.p.footerrow){$("table:first",$t.grid.sDiv).width($t.p.tblwidth);$t.grid.sDiv.scrollLeft=$t.grid.bDiv.scrollLeft}}})},hideCol:function(colname){return this.each(function(){$(this).ShowHideCol(colname,"none")})},showCol:function(colname){return this.each(function(){$(this).ShowHideCol(colname,"")})},setGridWidth:function(nwidth,shrink){return this.each(function(){var $t=this,cw,initwidth=0,brd=$t.p.cellLayout,lvc,vc=0,isSafari,hs=false,scw=$t.p.scrollOffset,aw,gw=0,tw=0,msw=$t.p.multiselectWidth,sgw=$t.p.subGridWidth,rnw=$t.p.rownumWidth,cl=$t.p.cellLayout,cr;if(!$t.grid){return}if(typeof shrink!="boolean"){shrink=$t.p.shrinkToFit}if(isNaN(nwidth)){return}if(nwidth==$t.grid.width){return}else{$t.grid.width=$t.p.width=nwidth}$("#gbox_"+$t.p.id).css("width",nwidth+"px");$("#gview_"+$t.p.id).css("width",nwidth+"px");$($t.grid.bDiv).css("width",nwidth+"px");$($t.grid.hDiv).css("width",nwidth+"px");if($t.p.pager){$($t.p.pager).css("width",nwidth+"px")}if($t.p.toolbar[0]===true){$($t.grid.uDiv).css("width",nwidth+"px");if($t.p.toolbar[1]=="both"){$($t.grid.ubDiv).css("width",nwidth+"px")}}if($t.p.footerrow){$($t.grid.sDiv).css("width",nwidth+"px")}if(shrink===false&&$t.p.forceFit==true){$t.p.forceFit=false}if(shrink===true){$.each($t.p.colModel,function(i){if(this.hidden===false){initwidth+=parseInt(this.width,10);vc++}});isSafari=$.browser.safari?true:false;if(isSafari){brd=0;msw+=cl;sgw+=cl;rnw+=cl}if($t.p.multiselect){tw=msw;gw=msw+brd;vc--}if($t.p.subGrid){tw+=sgw;gw+=sgw+brd;vc--}if($t.p.rownumbers){tw+=rnw;gw+=rnw+brd;vc--}$t.p.tblwidth=initwidth;aw=nwidth-brd*vc-gw;if(!isNaN($t.p.height)){if($($t.grid.bDiv)[0].clientHeight<$($t.grid.bDiv)[0].scrollHeight){hs=true;aw-=scw}}initwidth=0;var cl=$t.grid.cols.length>0;$.each($t.p.colModel,function(i){var tn=this.name;if(this.hidden===false&&tn!=="cb"&&tn!=="subgrid"&&tn!=="rn"){cw=Math.floor((aw)/($t.p.tblwidth-tw)*this.width);this.width=cw;initwidth+=cw;$t.grid.headers[i].width=cw;$t.grid.headers[i].el.style.width=cw+"px";if($t.p.footerrow){$t.grid.footers[i].style.width=cw+"px"}if(cl){$t.grid.cols[i].style.width=cw+"px"}lvc=i}});cr=0;if(hs&&nwidth-gw-(initwidth+brd*vc)!==scw){cr=nwidth-gw-(initwidth+brd*vc)-scw}else{if(Math.abs(nwidth-gw-(initwidth+brd*vc))!==1){cr=nwidth-gw-(initwidth+brd*vc)}}$t.p.colModel[lvc].width+=cr;cw=$t.p.colModel[lvc].width;$t.grid.headers[lvc].width=cw;$t.grid.headers[lvc].el.style.width=cw+"px";if(cl>0){$t.grid.cols[lvc].style.width=cw+"px"}$t.p.tblwidth=initwidth+tw+cr;$("table:first",$t.grid.bDiv).css("width",initwidth+tw+cr+"px");$("table:first",$t.grid.hDiv).css("width",initwidth+tw+cr+"px");$t.grid.hDiv.scrollLeft=$t.grid.bDiv.scrollLeft;if($t.p.footerrow){$t.grid.footers[lvc].style.width=cw+"px";$("table:first",$t.grid.sDiv).css("width",initwidth+tw+cr+"px")}}})},setGridHeight:function(nh){return this.each(function(){var $t=this;if(!$t.grid){return}$($t.grid.bDiv).css({height:nh+(isNaN(nh)?"":"px")});$t.p.height=nh})},setCaption:function(newcap){return this.each(function(){this.p.caption=newcap;$("span.ui-jqgrid-title",this.grid.cDiv).html(newcap);$(this.grid.cDiv).show()})},setLabel:function(colname,nData,prop,attrp){return this.each(function(){var $t=this,pos=-1;if(!$t.grid){return}if(isNaN(colname)){$($t.p.colModel).each(function(i){if(this.name==colname){pos=i;return false}})}else{pos=parseInt(colname,10)}if(pos>=0){var thecol=$("tr.ui-jqgrid-labels th:eq("+pos+")",$t.grid.hDiv);if(nData){$("div",thecol).html(nData);$t.p.colNames[pos]=nData}if(prop){if(typeof prop==="string"){$(thecol).addClass(prop)}else{$(thecol).css(prop)}}if(typeof attrp==="object"){$(thecol).attr(attrp)}}})},setCell:function(rowid,colname,nData,cssp,attrp){return this.each(function(){var $t=this,pos=-1,v;if(!$t.grid){return}if(isNaN(colname)){$($t.p.colModel).each(function(i){if(this.name==colname){pos=i;return false}})}else{pos=parseInt(colname,10)}if(pos>=0){var ind=$t.rows.namedItem(rowid);if(ind){var tcell=$("td:eq("+pos+")",ind);if(nData!==""){v=$t.formatter(rowid,nData,pos,ind,"edit");$(tcell).html(v).attr("title",$.jgrid.stripHtml(v))}if(cssp){if(typeof cssp==="string"){$(tcell).addClass(cssp)}else{$(tcell).css(cssp)}}if(typeof attrp==="object"){$(tcell).attr(attrp)}}}})},getCell:function(rowid,col){var ret=false;this.each(function(){var $t=this,pos=-1;if(!$t.grid){return}if(isNaN(col)){$($t.p.colModel).each(function(i){if(this.name===col){pos=i;return false}})}else{pos=parseInt(col,10)}if(pos>=0){var ind=$t.rows.namedItem(rowid);if(ind){ret=$.jgrid.htmlDecode($("td:eq("+pos+")",ind).html())}}});return ret},getCol:function(col){var ret=[];this.each(function(){var $t=this,pos=-1;if(!$t.grid){return}if(isNaN(col)){$($t.p.colModel).each(function(i){if(this.name===col){pos=i;return false}})}else{pos=parseInt(col,10)}if(pos>=0){var ln=$t.rows.length,i=0;if(ln&&ln>0){while(i0){if(nDotIndex<0){sOutput+=sDecimalSeparator;nDotIndex=sOutput.length-1}else{if(sDecimalSeparator!=="."){sOutput=sOutput.replace(".",sDecimalSeparator)}}while((sOutput.length-1-nDotIndex)-1)?nDotIndex:sOutput.length;var sNewOutput=sOutput.substring(nDotIndex);var nCount=-1;for(var i=nDotIndex;i>0;i--){nCount++;if((nCount%3===0)&&(i!==nDotIndex)&&(!bNegative||(i>1))){sNewOutput=sThousandsSeparator+sNewOutput}sNewOutput=sOutput.charAt(i-1)+sNewOutput}sOutput=sNewOutput}sOutput=(opts.prefix)?opts.prefix+sOutput:sOutput;sOutput=(opts.suffix)?sOutput+opts.suffix:sOutput;return sOutput}else{return nData}},DateFormat:function(format,date,newformat,opts){var token=/\\.|[dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU]/g,timezone=/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,timezoneClip=/[^-+\dA-Z]/g,pad=function(value,length){value=String(value);length=parseInt(length)||2;while(value.length11){date[k]=dM+1-12}}ts[format[k].toLowerCase()]=parseInt(date[k],10)}ts.m=parseInt(ts.m)-1;var ty=ts.y;if(ty>=70&&ty<=99){ts.y=1900+ts.y}else{if(ty>=0&&ty<=69){ts.y=2000+ts.y}}timestamp=new Date(ts.y,ts.m,ts.d,ts.h,ts.i,ts.s,0);if(opts.masks.newformat){newformat=opts.masks.newformat}else{if(!newformat){newformat="Y-m-d"}}var G=timestamp.getHours(),i=timestamp.getMinutes(),j=timestamp.getDate(),n=timestamp.getMonth()+1,o=timestamp.getTimezoneOffset(),s=timestamp.getSeconds(),u=timestamp.getMilliseconds(),w=timestamp.getDay(),Y=timestamp.getFullYear(),N=(w+6)%7+1,z=(new Date(Y,n-1,j)-new Date(Y,0,1))/86400000,flags={d:pad(j),D:dateFormat.i18n.dayNames[w],j:j,l:dateFormat.i18n.dayNames[w+7],N:N,S:opts.S(j),w:w,z:z,W:N<5?Math.floor((z+N-1)/7)+1:Math.floor((z+N-1)/7)||((new Date(Y-1,0,1).getDay()+6)%7<4?53:52),F:dateFormat.i18n.monthNames[n-1+12],m:pad(n),M:dateFormat.i18n.monthNames[n-1],n:n,t:"?",L:"?",o:"?",Y:Y,y:String(Y).substring(2),a:G<12?opts.AmPm[0]:opts.AmPm[1],A:G<12?opts.AmPm[2]:opts.AmPm[3],B:"?",g:G%12||12,G:G,h:pad(G%12||12),H:pad(G),i:pad(i),s:pad(s),u:u,e:"?",I:"?",O:(o>0?"-":"+")+pad(Math.floor(Math.abs(o)/60)*100+Math.abs(o)%60,4),P:"?",T:(String(timestamp).match(timezone)||[""]).pop().replace(timezoneClip,""),Z:"?",c:"?",r:"?",U:Math.floor(timestamp/1000)};return newformat.replace(token,function($0){return $0 in flags?flags[$0]:$0.substring(1)})}};$.fn.fmatter.defaultFormat=function(cellval,opts){return(isValue(cellval)&&cellval!=="")?cellval:opts.defaultValue?opts.defaultValue:" "};$.fn.fmatter.email=function(cellval,opts){if(!isEmpty(cellval)){return'
'+cellval+""}else{return $.fn.fmatter.defaultFormat(cellval,opts)}};$.fn.fmatter.checkbox=function(cval,opts){var op=$.extend({},opts.checkbox),ds;if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(op.disabled===true){ds="disabled"}else{ds=""}cval=cval+"";cval=cval.toLowerCase();var bchk=cval.search(/(false|0|no|off)/i)<0?" checked='checked' ":"";return'"},$.fn.fmatter.link=function(cellval,opts){var op={target:opts.target};var target="";if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(op.target){target="target="+op.target}if(!isEmpty(cellval)){return"'+cellval+""}else{return $.fn.fmatter.defaultFormat(cellval,opts)}};$.fn.fmatter.showlink=function(cellval,opts){var op={baseLinkUrl:opts.baseLinkUrl,showAction:opts.showAction,addParam:opts.addParam,target:opts.target,idName:opts.idName},target="";if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(op.target){target="target="+op.target}idUrl=op.baseLinkUrl+op.showAction+"?"+op.idName+"="+opts.rowId+op.addParam;if(isString(cellval)){return"'+cellval+""}else{return $.fn.fmatter.defaultFormat(cellval,opts)}};$.fn.fmatter.integer=function(cellval,opts){var op=$.extend({},opts.integer);if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(isEmpty(cellval)){return op.defaultValue}return $.fmatter.util.NumberFormat(cellval,op)};$.fn.fmatter.number=function(cellval,opts){var op=$.extend({},opts.number);if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(isEmpty(cellval)){return op.defaultValue}return $.fmatter.util.NumberFormat(cellval,op)};$.fn.fmatter.currency=function(cellval,opts){var op=$.extend({},opts.currency);if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(isEmpty(cellval)){return op.defaultValue}return $.fmatter.util.NumberFormat(cellval,op)};$.fn.fmatter.date=function(cellval,opts,act){var op=$.extend({},opts.date);if(!isUndefined(opts.colModel.formatoptions)){op=$.extend({},op,opts.colModel.formatoptions)}if(!op.reformatAfterEdit&&act=="edit"){return $.fn.fmatter.defaultFormat(cellval,opts)}else{if(!isEmpty(cellval)){return $.fmatter.util.DateFormat(op.srcformat,cellval,op.newformat,op)}else{return $.fn.fmatter.defaultFormat(cellval,opts)}}};$.fn.fmatter.select=function(cellval,opts,act){if(act=="edit"){return $.fn.fmatter.defaultFormat(cellval,opts)}else{if(!isEmpty(cellval)){var oSelect=false;if(!isUndefined(opts.colModel.editoptions)){oSelect=opts.colModel.editoptions.value}if(oSelect){var ret=[],msl=opts.colModel.editoptions.multiple===true?true:false,scell=[],sv;if(msl){scell=cellval.split(",");scell=$.map(scell,function(n){return $.trim(n)})}if(isString(oSelect)){var so=oSelect.split(";"),j=0;for(var i=0;i-1){ret[j]=sv[1];j++}}else{if($.trim(sv[0])==$.trim(cellval)){ret[0]=sv[1];break}}}}else{if(isObject(oSelect)){if(msl){ret=jQuery.map(scel,function(n,i){return oSelect[n]})}ret[0]=oSelect[cellval]||""}}return ret.join(", ")}else{return $.fn.fmatter.defaultFormat(cellval,opts)}}}};$.unformat=function(cellval,options,pos,cnt){var ret,formatType=options.colModel.formatter,op=options.colModel.formatoptions||{},unformatFunc=options.colModel.unformat||($.fn.fmatter[formatType]&&$.fn.fmatter[formatType].unformat);if(unformatFunc!=="undefined"&&isFunction(unformatFunc)){ret=unformatFunc($(cellval).text(),options)}else{if(formatType!=="undefined"&&isString(formatType)){var opts=$.jgrid.formatter||{},stripTag;switch(formatType){case"integer":op=$.extend({},opts.integer,op);stripTag=eval("/"+op.thousandsSeparator+"/g");ret=$(cellval).text().replace(stripTag,"");break;case"number":op=$.extend({},opts.number,op);stripTag=eval("/"+op.thousandsSeparator+"/g");ret=$(cellval).text().replace(op.decimalSeparator,".").replace(stripTag,"");break;case"currency":op=$.extend({},opts.currency,op);stripTag=eval("/"+op.thousandsSeparator+"/g");ret=$(cellval).text().replace(op.decimalSeparator,".").replace(op.prefix,"").replace(op.suffix,"").replace(stripTag,"");break;case"checkbox":var cbv=(options.colModel.editoptions)?options.colModel.editoptions.value.split(":"):["Yes","No"];ret=$("input",cellval).attr("checked")?cbv[0]:cbv[1];break;default:ret=$(cellval).text();break}}}return ret?ret:cnt===true?$(cellval).text():$.jgrid.htmlDecode($(cellval).html())};function fireFormatter(formatType,cellval,opts,rwd,act){formatType=formatType.toLowerCase();var v=cellval;if($.fn.fmatter[formatType]){v=$.fn.fmatter[formatType](cellval,opts,act)}return v}function debug($obj){if(window.console&&window.console.log){window.console.log($obj)}}isValue=function(o){return(isObject(o)||isString(o)||isNumber(o)||isBoolean(o))};isBoolean=function(o){return typeof o==="boolean"};isNull=function(o){return o===null};isNumber=function(o){return typeof o==="number"&&isFinite(o)};isString=function(o){return typeof o==="string"};isEmpty=function(o){if(!isString(o)&&isValue(o)){return false}else{if(!isValue(o)){return true}}o=$.trim(o).replace(/\ \;/ig,"").replace(/\ \;/ig,"");return o===""};isUndefined=function(o){return typeof o==="undefined"};isObject=function(o){return(o&&(typeof o==="object"||isFunction(o)))||false};isFunction=function(o){return typeof o==="function"}})(jQuery);(function(a){a.fn.extend({getColProp:function(d){var b={},f=this[0];if(!f.grid){return}var e=f.p.colModel;for(var c=0;c0){a(e).each(function(m){i=this;k=h.rows.namedItem(i[c]);if(k){g=i[c];if(d===true){if(h.p.jsonReader.repeatitems===true){if(h.p.jsonReader.cell){i=i[h.p.jsonReader.cell]}for(var l=0;l span:first",k).html(j).attr("title",a.jgrid.stripHtml(j))}else{a("td:eq("+l+")",k).html(j).attr("title",a.jgrid.stripHtml(j))}}f=true;return true}}a(h.p.colModel).each(function(n){b=d===true?this.jsonmap||this.name:this.name;if(i[b]!=undefined){j=h.formatter(g,i[b],n,i,"edit");if(h.p.treeGrid===true&&b==h.p.ExpandColumn){a("td:eq("+n+") > span:first",k).html(j).attr("title",a.jgrid.stripHtml(j))}else{a("td:eq("+n+")",k).html(j).attr("title",a.jgrid.stripHtml(j))}f=true}})}})}});return f},filterGrid:function(c,b){b=a.extend({gridModel:false,gridNames:false,gridToolbar:false,filterModel:[],formtype:"horizontal",autosearch:true,formclass:"filterform",tableclass:"filtertable",buttonclass:"filterbutton",searchButton:"Search",clearButton:"Clear",enableSearch:false,enableClear:false,beforeSearch:null,afterSearch:null,beforeClear:null,afterClear:null,url:"",marksearched:true},b||{});return this.each(function(){var l=this;this.p=b;if(this.p.filterModel.length==0&&this.p.gridModel===false){alert("No filter is set");return}if(!c){alert("No target grid is set!");return}this.p.gridid=c.indexOf("#")!=-1?c:"#"+c;var d=a(this.p.gridid).getGridParam("colModel");if(d){if(this.p.gridModel===true){var e=a(this.p.gridid)[0];var g;a.each(d,function(o,p){var m=[];this.search=this.search===false?false:true;if(this.editrules&&this.editrules.searchhidden===true){g=true}else{if(this.hidden===true){g=false}else{g=true}}if(this.search===true&&g===true){if(l.p.gridNames===true){m.label=e.p.colNames[o]}else{m.label=""}m.name=this.name;m.index=this.index||this.name;m.stype=this.edittype||"text";if(m.stype!="select"){m.stype="text"}m.defval=this.defval||"";m.surl=this.surl||"";m.sopt=this.editoptions||{};m.width=this.width;l.p.filterModel.push(m)}})}else{a.each(l.p.filterModel,function(o,p){for(var m=0;m0?true:false;a.extend(o.p.postData,q);var s;if(l.p.url){s=a(o).getGridParam("url");a(o).setGridParam({url:l.p.url})}a(o).setGridParam({search:r,page:1}).trigger("reloadGrid");if(s){a(o).setGridParam({url:s})}if(a.isFunction(l.p.afterSearch)){l.p.afterSearch()}};var k=function(){var q={},n,p=0;var o=a(l.p.gridid)[0],m;if(a.isFunction(l.p.beforeClear)){l.p.beforeClear()}a.each(l.p.filterModel,function(t,w){m=this.index;n=(this.defval)?this.defval:"";if(!this.stype){this.stype=="text"}switch(this.stype){case"select":var v;a("select[name="+m+"] option",l).each(function(x){if(x==0){this.selected=true}if(a(this).text()==n){this.selected=true;v=a(this).val();return false}});if(v){q[m]=v;if(l.p.marksearched){a("#jqgh_"+this.name,o.grid.hDiv).addClass("dirty-cell")}p++}else{if(l.p.marksearched){a("#jqgh_"+this.name,o.grid.hDiv).removeClass("dirty-cell")}try{delete o.p.postData[this.index]}catch(u){}}break;case"text":a("input[name="+m+"]",l).val(n);if(n){q[m]=n;if(l.p.marksearched){a("#jqgh_"+this.name,o.grid.hDiv).addClass("dirty-cell")}p++}else{if(l.p.marksearched){a("#jqgh_"+this.name,o.grid.hDiv).removeClass("dirty-cell")}try{delete o.p.postData[this.index]}catch(u){}}break}});var r=p>0?true:false;a.extend(o.p.postData,q);var s;if(l.p.url){s=a(o).getGridParam("url");a(o).setGridParam({url:l.p.url})}a(o).setGridParam({search:r,page:1}).trigger("reloadGrid");if(s){a(o).setGridParam({url:s})}if(a.isFunction(l.p.afterClear)){l.p.afterClear()}};var i=function(){var q=document.createElement("tr");var n,s,m,o,r,p;if(l.p.formtype=="horizontal"){a(f).append(q)}a.each(l.p.filterModel,function(A,v){o=document.createElement("td");a(o).append("");r=document.createElement("td");var z=this;if(!this.stype){this.stype="text"}switch(this.stype){case"select":if(this.surl){a(r).load(this.surl,function(){if(z.defval){a("select",this).val(z.defval)}a("select",this).attr({name:z.index||z.name,id:"sg_"+z.name});if(z.sopt){a("select",this).attr(z.sopt)}if(l.p.gridToolbar===true&&z.width){a("select",this).width(z.width)}if(l.p.autosearch===true){a("select",this).change(function(E){h();return false})}})}else{if(z.sopt.value){var t=z.sopt.value;var w=document.createElement("select");a(w).attr({name:z.index||z.name,id:"sg_"+z.name}).attr(z.sopt);if(typeof t==="string"){var u=t.split(";"),D,x;for(var y=0;y");if(z.sopt){a("input",r).attr(z.sopt)}if(l.p.gridToolbar===true&&z.width){if(a.browser.msie){a("input",r).width(z.width-4)}else{a("input",r).width(z.width-2)}}if(l.p.autosearch===true){a("input",r).keypress(function(F){var E=F.charCode?F.charCode:F.keyCode?F.keyCode:0;if(E==13){h();return false}return this})}break}if(l.p.formtype=="horizontal"){if(l.p.gridToolbar===true&&l.p.gridNames===false){a(q).append(r)}else{a(q).append(o).append(r)}a(q).append(r)}else{n=document.createElement("tr");a(n).append(o).append(r);a(f).append(n)}});r=document.createElement("td");if(l.p.enableSearch===true){s="";a(r).append(s);a("input#sButton",r).click(function(){h();return false})}if(l.p.enableClear===true){m="";a(r).append(m);a("input#cButton",r).click(function(){k();return false})}if(l.p.enableClear===true||l.p.enableSearch===true){if(l.p.formtype=="horizontal"){a(q).append(r)}else{n=document.createElement("tr");a(n).append(" ").append(r);a(f).append(n)}}};var j=a("
");var f=a("
");a(j).append(f);i();a(this).append(j);this.triggerSearch=function(){h()};this.clearSearch=function(){k()}})},filterToolbar:function(b){b=a.extend({autosearch:true,beforeSearch:null,afterSearch:null,beforeClear:null,afterClear:null,searchurl:""},b||{});return this.each(function(){var g=this;var c=function(){var o={},n=0,m,l;g.p.searchdata={};if(a.isFunction(b.beforeSearch)){b.beforeSearch()}a.each(g.p.colModel,function(r,t){l=this.index||this.name;switch(this.stype){case"select":m=a("select[name="+l+"]",g.grid.hDiv).val();if(m){o[l]=m;n++}else{try{delete g.p.postData[l]}catch(s){}}break;case"text":m=a("input[name="+l+"]",g.grid.hDiv).val();if(m){o[l]=m;n++}else{try{delete g.p.postData[l]}catch(s){}}break}});var p=n>0?true:false;a.extend(g.p.postData,o);var q;if(g.p.searchurl){q=g.p.url;a(g).setGridParam({url:g.p.searchurl})}a(g).setGridParam({search:p,page:1}).trigger("reloadGrid");if(q){a(g).setGridParam({url:q})}if(a.isFunction(b.afterSearch)){b.afterSearch()}};var j=function(){var o={},m,n=0,l;if(a.isFunction(b.beforeClear)){b.beforeClear()}a.each(g.p.colModel,function(r,u){m=(this.searchoptions&&this.searchoptions.defaultValue)?this.searchoptions.defaultValue:"";l=this.index||this.name;switch(this.stype){case"select":var t;a("select[name="+l+"] option",g.grid.hDiv).each(function(v){if(v==0){this.selected=true}if(a(this).text()==m){this.selected=true;t=a(this).val();return false}});if(t){o[l]=t;n++}else{try{delete g.p.postData[l]}catch(s){}}break;case"text":a("input[name="+l+"]",g.grid.hDiv).val(m);if(m){o[l]=m;n++}else{try{delete g.p.postData[l]}catch(s){}}break}});var p=n>0?true:false;a.extend(g.p.postData,o);var q;if(g.p.searchurl){q=g.p.url;a(g).setGridParam({url:g.p.searchurl})}a(g).setGridParam({search:p,page:1}).trigger("reloadGrid");if(q){a(g).setGridParam({url:q})}if(a.isFunction(b.afterClear)){b.afterClear()}};var k=function(){var l=a("tr.ui-search-toolbar",g.grid.hDiv);if(l.css("display")=="none"){l.show()}else{l.hide()}};function f(l,n){var m=a(l);if(m[0]!=null){jQuery.each(n,function(){if(this.data!=null){m.bind(this.type,this.data,this.fn)}else{m.bind(this.type,this.fn)}})}}var h=a(""),d,i,e;a.each(g.p.colModel,function(s,o){var u=this;d=a("");i=a("
");if(this.hidden===true){a(d).css("display","none")}this.search=this.search===false?false:true;if(typeof this.stype=="undefined"){this.stype="text"}e=a.extend({},this.searchoptions||{});if(this.search){switch(this.stype){case"select":if(this.surl){a(i).load(this.surl,{_nsd:(new Date().getTime())},function(){if(e.defaultValue){a("select",this).val(e.defaultValue)}a("select",this).attr({name:u.index||u.name,id:"gs_"+u.name});if(e.attr){a("select",this).attr(e.attr)}a("select",this).css({width:"100%"});if(e.dataInit!=null){e.dataInit(a("select",this)[0])}if(e.dataEvents!=null){f(a("select",this)[0],e.dataEvents)}if(b.autosearch===true){a("select",this).change(function(n){c();return false})}})}else{if(u.editoptions&&u.editoptions.value){var l=u.editoptions.value,p=document.createElement("select");p.style.width="100%";a(p).attr({name:u.index||u.name,id:"gs_"+u.name});if(typeof l==="string"){var m=l.split(";"),w,q;for(var r=0;r");if(e.attr){a("input",i).attr(e.attr)}if(e.dataInit!=null){e.dataInit(a("input",i)[0])}if(e.dataEvents!=null){f(a("input",i)[0],e.dataEvents)}if(b.autosearch===true){a("input",i).keypress(function(x){var n=x.charCode?x.charCode:x.keyCode?x.keyCode:0;if(n==13){c();return false}return this})}break}}a(d).append(i);a(h).append(d)});a("table thead",g.grid.hDiv).append(h);this.triggerToolbar=function(){c()};this.clearToolbar=function(){j()};this.toggleToolbar=function(){k()}})}})})(jQuery);var showModal=function(a){a.w.show()};var closeModal=function(a){a.w.hide().attr("aria-hidden","true");if(a.o){a.o.remove()}};var createModal=function(i,d,a,k,m,l){var h=document.createElement("div");h.className="ui-widget ui-widget-content ui-corner-all ui-jqdialog";h.id=i.themodal;var b=document.createElement("div");b.className="ui-jqdialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix";b.id=i.modalhead;jQuery(b).append(""+a.caption+"");var j=jQuery("").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).append("");jQuery(b).append(j);var g=document.createElement("div");jQuery(g).addClass("ui-jqdialog-content ui-widget-content").attr("id",i.modalcontent);jQuery(g).append(d);h.appendChild(g);jQuery(h).prepend(b);if(l===true){jQuery("body").append(h)}else{jQuery(h).insertBefore(k)}if(typeof a.jqModal==="undefined"){a.jqModal=true}if(jQuery.fn.jqm&&a.jqModal===true){if(a.left==0&&a.top==0){var f=[];f=findPos(m);a.left=f[0]+4;a.top=f[1]+4}}jQuery("a.ui-jqdialog-titlebar-close",b).click(function(o){var n=jQuery("#"+i.themodal).data("onClose")||a.onClose;var p=jQuery("#"+i.themodal).data("gbox")||a.gbox;hideModal("#"+i.themodal,{gb:p,jqm:a.jqModal,onClose:n});return false});if(a.width==0||!a.width){a.width=300}if(a.height==0||!a.height){a.height=200}if(!a.zIndex){a.zIndex=950}jQuery(h).css({top:a.top+"px",left:a.left+"px",width:isNaN(a.width)?"auto":a.width+"px",height:isNaN(a.height)?"auto":a.height+"px",zIndex:a.zIndex,overflow:"hidden"}).attr({tabIndex:"-1",role:"dialog","aria-labelledby":i.modalhead,"aria-hidden":"true"});if(typeof a.drag=="undefined"){a.drag=true}if(typeof a.resize=="undefined"){a.resize=true}if(a.drag){jQuery(b).css("cursor","move");if(jQuery.fn.jqDrag){jQuery(h).jqDrag(b)}else{try{jQuery(h).draggable({handle:jQuery("#"+b.id)})}catch(c){}}}if(a.resize){if(jQuery.fn.jqResize){jQuery(h).append("
");jQuery("#"+i.themodal).jqResize(".jqResize",i.scrollelm?"#"+i.scrollelm:false)}else{try{jQuery(h).resizable({handles:"se",alsoResize:i.scrollelm?"#"+i.scrollelm:false})}catch(c){}}}if(a.closeOnEscape===true){jQuery(h).keydown(function(o){if(o.which==27){var n=jQuery("#"+i.themodal).data("onClose")||a.onClose;hideModal(this,{gb:a.gbox,jqm:a.jqModal,onClose:n})}})}};var viewModal=function(a,c){c=jQuery.extend({toTop:true,overlay:10,modal:false,onShow:showModal,onHide:closeModal,gbox:"",jqm:true,jqM:true},c||{});if(jQuery.fn.jqm&&c.jqm==true){if(c.jqM){jQuery(a).attr("aria-hidden","false").jqm(c).jqmShow()}else{jQuery(a).attr("aria-hidden","false").jqmShow()}}else{if(c.gbox!=""){jQuery(".jqgrid-overlay:first",c.gbox).show();jQuery(a).data("gbox",c.gbox)}jQuery(a).show().attr("aria-hidden","false");try{jQuery(":input:visible",a)[0].focus()}catch(b){}}return false};var hideModal=function(a,d){d=jQuery.extend({jqm:true,gb:""},d||{});if(d.onClose){var b=d.onClose(a);if(typeof b=="boolean"&&!b){return}}if(jQuery.fn.jqm&&d.jqm===true){jQuery(a).attr("aria-hidden","true").jqmHide()}else{if(d.gb!=""){try{jQuery(".jqgrid-overlay:first",d.gb).hide()}catch(c){}}jQuery(a).hide().attr("aria-hidden","true")}};function info_dialog(a,d,b){var c="
";c+="

"+d+"

";c+="";c+="
";createModal({themodal:"info_dialog",modalhead:"info_head",modalcontent:"info_content"},c,{width:290,height:120,drag:false,resize:false,caption:""+a+"",left:250,top:170,closeOnEscape:true},"","",true);jQuery("#closedialog","#info_id").addClass("ui-state-default ui-corner-all").height(21).css({padding:" .2em .5em",cursor:"pointer"}).hover(function(){jQuery(this).addClass("ui-state-hover")},function(){jQuery(this).removeClass("ui-state-hover")});if(jQuery.fn.jqm){}else{jQuery("#closedialog","#info_id").click(function(f){hideModal("#info_dialog");return false})}viewModal("#info_dialog",{onHide:function(e){e.w.hide().remove();if(e.o){e.o.remove()}},modal:true})}function findPos(a){var b=curtop=0;if(a.offsetParent){do{b+=a.offsetLeft;curtop+=a.offsetTop}while(a=a.offsetParent)}return[b,curtop]}function isArray(a){if(a.constructor.toString().indexOf("Array")==-1){return false}else{return true}}function createEl(h,r,f){var g="";if(r.defaultValue){delete r.defaultValue}function o(i,e){if(jQuery.isFunction(e.dataInit)){i.id=e.id;e.dataInit(i);delete e.id;delete e.dataInit}if(e.dataEvents){jQuery.each(e.dataEvents,function(){if(this.data!=null){jQuery(i).bind(this.type,this.data,this.fn)}else{jQuery(i).bind(this.type,this.fn)}});delete e.dataEvents}return e}switch(h){case"textarea":g=document.createElement("textarea");if(!r.cols){jQuery(g).css("width","98%")}if(f==" "||f==" "||(f.length==1&&f.charCodeAt(0)==160)){f=""}g.value=f;r=o(g,r);jQuery(g).attr(r);break;case"checkbox":g=document.createElement("input");g.type="checkbox";if(!r.value){var p=f.toLowerCase();if(p.search(/(false|0|no|off|undefined)/i)<0&&p!==""){g.checked=true;g.defaultChecked=true;g.value=f}else{g.value="on"}jQuery(g).attr("offval","off")}else{var a=r.value.split(":");if(f===a[0]){g.checked=true;g.defaultChecked=true}g.value=a[0];jQuery(g).attr("offval",a[1]);try{delete r.value}catch(l){}}r=o(g,r);jQuery(g).attr(r);break;case"select":g=document.createElement("select");var q=r.multiple===true?true:false;if(r.dataUrl!=null){jQuery.get(r.dataUrl,{_nsd:(new Date().getTime())},function(s){try{delete r.dataUrl;delete r.value}catch(t){}var i=jQuery(s).html();jQuery(g).append(i);r=o(g,r);if(typeof r.size==="undefined"){r.size=q?3:1}jQuery(g).attr(r);setTimeout(function(){jQuery("option",g).each(function(e){if(jQuery(this).html()==f){this.selected="selected";return false}})},0)},"html")}else{if(r.value){var j=[],k;if(q){j=f.split(",");j=jQuery.map(j,function(e){return jQuery.trim(e)});if(typeof r.size==="undefined"){r.size=3}}else{r.size=1}if(typeof r.value==="string"){var c=r.value.split(";"),n,d;try{delete r.value}catch(l){}r=o(g,r);jQuery(g).attr(r);for(k=0;k-1){d.selected="selected"}g.appendChild(d)}}else{if(typeof r.value==="object"){var b=r.value;try{delete r.value}catch(l){}r=o(g,r);jQuery(g).attr(r);k=0;for(var m in b){k++;d=document.createElement("option");d.value=m;d.innerHTML=b[m];if(!q&&b[m]==f){d.selected="selected"}if(q&&jQuery.inArray(jQuery.trim(b[m]),j)>-1){d.selected="selected"}g.appendChild(d)}}}}}break;case"text":case"password":case"button":g=document.createElement("input");g.type=h;g.value=jQuery.jgrid.htmlDecode(f);r=o(g,r);if(!r.size){jQuery(g).css({width:"98%"})}jQuery(g).attr(r);break;case"image":case"file":g=document.createElement("input");g.type=h;r=o(g,r);jQuery(g).attr(r);break}return g}function checkValues(c,l,j){var f,h,m;if(typeof(l)=="string"){for(h=0,len=j.p.colModel.length;h=0){var f=j.p.colModel[l].editrules}}if(f){if(!m){m=j.p.colNames[l]}if(f.required===true){if(c.match(/^s+$/)||c==""){return[false,m+": "+jQuery.jgrid.edit.msg.required,""]}}var d=f.required===false?false:true;if(f.number===true){if(!(d===false&&isEmpty(c))){if(isNaN(c)){return[false,m+": "+jQuery.jgrid.edit.msg.number,""]}}}if(typeof f.minValue!="undefined"&&!isNaN(f.minValue)){if(parseFloat(c)parseFloat(f.maxValue)){return[false,m+": "+jQuery.jgrid.edit.msg.maxValue+" "+f.maxValue,""]}}var a;if(f.email===true){if(!(d===false&&isEmpty(c))){a=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i;if(!a.test(c)){return[false,m+": "+jQuery.jgrid.edit.msg.email,""]}}}if(f.integer===true){if(!(d===false&&isEmpty(c))){if(isNaN(c)){return[false,m+": "+jQuery.jgrid.edit.msg.integer,""]}if((c%1!=0)||(c.indexOf(".")!=-1)){return[false,m+": "+jQuery.jgrid.edit.msg.integer,""]}}}if(f.date===true){if(!(d===false&&isEmpty(c))){var b=j.p.colModel[l].datefmt||"Y-m-d";if(!checkDate(b,c)){return[false,m+": "+jQuery.jgrid.edit.msg.date+" - "+b,""]}}}if(f.url===true){if(!(d===false&&isEmpty(c))){a=/^(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;if(!a.test(c)){return[false,m+": "+jQuery.jgrid.edit.msg.url,""]}}}}return[true,"",""]}function checkDate(l,c){var e={},n;l=l.toLowerCase();if(l.indexOf("/")!=-1){n="/"}else{if(l.indexOf("-")!=-1){n="-"}else{if(l.indexOf(".")!=-1){n="."}else{n="/"}}}l=l.split(n);c=c.split(n);if(c.length!=3){return false}var f=-1,m,g=-1,d=-1;for(var h=0;h12){return false}}if(g===-1){return false}else{k=e[l[g]].toString();if(k.length<1||e[l[g]]<1||e[l[g]]>31||(e[l[d]]==2&&e[l[g]]>daysInFebruary(e[l[f]]))||e[l[g]]>a[e[l[d]]]){return false}}return true}function daysInFebruary(a){return(((a%4==0)&&((!(a%100==0))||(a%400==0)))?29:28)}function DaysArray(b){for(var a=1;a<=b;a++){this[a]=31;if(a==4||a==6||a==9||a==11){this[a]=30}if(a==2){this[a]=29}}return this}function isEmpty(a){if(a.match(/^s+$/)||a==""){return true}else{return false}};(function(b){var a=null;b.fn.extend({searchGrid:function(c){c=b.extend({recreateFilter:false,drag:true,sField:"searchField",sValue:"searchString",sOper:"searchOper",sFilter:"filters",beforeShowSearch:null,afterShowSearch:null,onInitializeSearch:null,closeAfterSearch:false,closeOnEscape:false,multipleSearch:false,sopt:null,onClose:null},b.jgrid.search,c||{});return this.each(function(){var l=this;if(!l.grid){return}if(b.fn.searchFilter){var g="fbox_"+l.p.id;if(c.recreateFilter===true){b("#"+g).remove()}if(b("#"+g).html()!=null){if(b.isFunction(c.beforeShowSearch)){c.beforeShowSearch(b("#"+g))}f();if(b.isFunction(c.afterShowSearch)){c.afterShowSearch(b("#"+g))}}else{var n=[],u=jQuery("#"+l.p.id).getGridParam("colNames"),r=jQuery("#"+l.p.id).getGridParam("colModel"),t=["eq","ne","lt","le","gt","ge","bw","bn","in","ni","ew","en","cn","nc"],i,q,h;b.each(r,function(x,C){var z=(typeof C.search==="undefined")?true:C.search,y=(C.hidden===true),k=b.extend({},{text:u[x],value:C.index||C.name},this.searchoptions),w=(k.searchhidden===true)||true;if(typeof k.sopt=="undefined"){k.sopt=t}h=0;k.ops=[];for(i=0;i0){var p=jQuery.fn.searchFilter.defaults.operators;if(c.sopt!=null){p=[];h=0;for(i=0;c.sopt.length<0;i++){if((q=b.inArray(c.sopt[i],t))!=-1){p[h]={op:c.sopt[i],text:c.odata[q]};h++}}}b("").insertBefore("#gview_"+l.p.id);jQuery("#"+g).searchFilter(n,{groupOps:c.groupOps,operators:p,onClose:d,resetText:c.Reset,searchText:c.Find,windowTitle:c.caption,rulesText:c.rulesText,matchText:c.matchText,onSearch:s,onReset:m,stringResult:c.multipleSearch});b(".ui-widget-overlay","#"+g).remove();if(c.drag===true){b("#"+g+" table thead tr:first td:first").css("cursor","move");if(jQuery.fn.jqDrag){jQuery("#"+g).jqDrag(b("#"+g+" table thead tr:first td:first"))}else{try{b("#"+g).draggable({handle:jQuery("#"+g+" table thead tr:first td:first")})}catch(o){}}}if(c.multipleSearch===false){b(".ui-del, .ui-add, .ui-del, .ui-add-last, .matchText, .rulesText","#"+g).hide();b("select[name='groupOp']","#"+g).hide()}if(b.isFunction(c.onInitializeSearch)){c.onInitializeSearch(b("#"+g))}if(b.isFunction(c.beforeShowSearch)){c.beforeShowSearch(b("#"+g))}f();if(b.isFunction(c.afterShowSearch)){c.afterShowSearch(b("#"+g))}if(c.closeOnEscape===true){jQuery("#"+g).keydown(function(j){if(j.which==27){d(b("#"+g))}})}}}}function s(v){var e=(v!==undefined),k=jQuery("#"+l.p.id),j={};if(c.multipleSearch===false){j[c.sField]=v.rules[0].field;j[c.sValue]=v.rules[0].data;j[c.sOper]=v.rules[0].op}else{j[c.sFilter]=v}k[0].p.search=e;b.extend(k[0].p.postData,j);k[0].p.page=1;k.trigger("reloadGrid");if(c.closeAfterSearch){d(b("#"+g))}}function m(v){var e=(v!==undefined),k=jQuery("#"+l.p.id),j=[];k[0].p.search=e;if(c.multipleSearch===false){j[c.sField]=j[c.sValue]=j[c.sOper]=""}else{j[c.sFilter]=""}b.extend(k[0].p.postData,j);k[0].p.page=1;k.trigger("reloadGrid")}function d(e){if(c.onClose){var j=c.onClose(e);if(typeof j=="boolean"&&!j){return}}e.hide();b(".jqgrid-overlay","#gbox_"+l.p.id).hide()}function f(){b("#"+g).show();b(".jqgrid-overlay","#gbox_"+l.p.id).show();try{b(":input:visible","#"+g)[0].focus()}catch(e){}}})},editGridRow:function(c,d){d=b.extend({top:0,left:0,width:300,height:"auto",dataheight:"auto",modal:false,drag:true,resize:true,url:null,mtype:"POST",closeAfterAdd:false,clearAfterAdd:true,closeAfterEdit:false,reloadAfterSubmit:true,onInitializeForm:null,beforeInitData:null,beforeShowForm:null,afterShowForm:null,beforeSubmit:null,afterSubmit:null,onclickSubmit:null,afterComplete:null,onclickPgButtons:null,afterclickPgButtons:null,editData:{},recreateForm:false,jqModal:true,closeOnEscape:false,addedrow:"first",topinfo:"",bottominfo:"",saveicon:[],closeicon:[],savekey:[false,13],navkeys:[false,38,40],checkOnSubmit:false,checkOnUpdate:false,_savedData:{},onClose:null},b.jgrid.edit,d||{});a=d;return this.each(function(){var e=this;if(!e.grid||!c){return}var B=e.p.id,x="FrmGrid_"+B,t="TblGrid_"+B,h={themodal:"editmod"+B,modalhead:"edithd"+B,modalcontent:"editcnt"+B,scrollelm:x},C=b.isFunction(a.beforeShowForm)?a.beforeShowForm:false,N=b.isFunction(a.afterShowForm)?a.afterShowForm:false,M=b.isFunction(a.beforeInitData)?a.beforeInitData:false,n=b.isFunction(a.onInitializeForm)?a.onInitializeForm:false,H=null,I=1,p=0,u,D,E,Q,G,A;if(c=="new"){c="_empty";d.caption=d.addCaption}else{d.caption=d.editCaption}if(d.recreateForm===true&&b("#"+h.themodal).html()!=null){b("#"+h.themodal).remove()}var j=true;if(d.checkOnUpdate&&d.jqModal&&!d.modal){j=false}if(b("#"+h.themodal).html()!=null){b(".ui-jqdialog-title","#"+h.modalhead).html(d.caption);b("#FormError","#"+t).hide();if(M){M(b("#"+x))}m(c,e,x);if(c=="_empty"){b("#pData, #nData","#"+t+"_2").hide()}else{b("#pData, #nData","#"+t+"_2").show()}if(d.processing===true){d.processing=false;b("#sData","#"+t).removeClass("ui-state-active")}if(b("#"+x).data("disabled")===true){b(".confirm","#"+h.themodal).hide();b("#"+x).data("disabled",false)}if(C){C(b("#"+x))}b("#"+h.themodal).data("onClose",a.onClose);viewModal("#"+h.themodal,{gbox:"#gbox_"+B,jqm:d.jqModal,jqM:false,closeoverlay:j,modal:d.modal});if(!j){b(".jqmOverlay").click(function(){if(!f()){return false}hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose});return false})}if(N){N(b("#"+x))}}else{b(e.p.colModel).each(function(V){var W=this.formoptions;I=Math.max(I,W?W.colpos||0:0);p=Math.max(p,W?W.rowpos||0:0)});var q=isNaN(d.dataheight)?d.dataheight:d.dataheight+"px";var L,S=b("
").data("disabled",false),z=b("
");b(S).append(z);L=b("");L[0].rp=0;b(z).append(L);if(a.topinfo){L=b(""+a.topinfo+"");L[0].rp=0;b(z).append(L)}if(M){M(b("#"+x))}var y=r(c,e,z,I),k="",l="",g=""+d.bSubmit+"",s=""+d.bCancel+"";var P="";if(a.bottominfo){P+=""}P+="
"+g+" "+s+"
"+a.bottominfo+"
";if(p>0){var w=[];b.each(b(z)[0].rows,function(V,W){w[V]=W});w.sort(function(W,V){if(W.rp>V.rp){return 1}if(W.rp").append(S).append(P);createModal(h,O,d,"#gview_"+e.p.id,b("#gview_"+e.p.id)[0]);O=null;P=null;jQuery("#"+h.themodal).keydown(function(V){if(b("#"+x).data("disabled")===true){return false}if(a.savekey[0]===true&&V.which==a.savekey[1]){b("#sData","#"+t+"_2").trigger("click");return false}if(V.which===27){if(!f()){return false}if(o){hideModal(this,{gb:d.gbox,jqm:d.jqModal,onClose:a.onClose})}return false}if(a.navkeys[0]===true){if(b("#id_g","#"+t).val()=="_empty"){return true}if(V.which==a.navkeys[1]){b("#pData","#"+t+"_2").trigger("click");return false}if(V.which==a.navkeys[2]){b("#nData","#"+t+"_2").trigger("click");return false}}});if(d.checkOnUpdate){b("a.ui-jqdialog-titlebar-close span","#"+h.themodal).removeClass("jqmClose");b("a.ui-jqdialog-titlebar-close","#"+h.themodal).unbind("click").click(function(){if(!f()){return false}hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose});return false})}d.saveicon=b.extend([true,"left","ui-icon-disk"],d.saveicon);d.closeicon=b.extend([true,"left","ui-icon-close"],d.closeicon);if(d.saveicon[0]==true){b("#sData","#"+t+"_2").addClass(d.saveicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("")}if(d.closeicon[0]==true){b("#cData","#"+t+"_2").addClass(d.closeicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("")}if(a.checkOnSubmit||a.checkOnUpdate){g=""+d.bYes+"";l=""+d.bNo+"";s=""+d.bExit+"";var F,v=d.zIndex||999;v++;if(b.browser.msie&&b.browser.version==6){F=''}else{F=""}b("
"+d.saveData+"

"+g+l+s+"
").insertAfter("#"+x);b("#sNew","#"+h.themodal).click(function(){i([true,"",""]);b("#"+x).data("disabled",false);b(".confirm","#"+h.themodal).hide();return false});b("#nNew","#"+h.themodal).click(function(){b(".confirm","#"+h.themodal).hide();b("#"+x).data("disabled",false);setTimeout(function(){b(":input","#"+x)[0].focus()},0);return false});b("#cNew","#"+h.themodal).click(function(){b(".confirm","#"+h.themodal).hide();b("#"+x).data("disabled",false);hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose});return false})}if(n){n(b("#"+x))}if(c=="_empty"){b("#pData,#nData","#"+t+"_2").hide()}else{b("#pData,#nData","#"+t+"_2").show()}if(C){C(b("#"+x))}b("#"+h.themodal).data("onClose",a.onClose);viewModal("#"+h.themodal,{gbox:"#gbox_"+B,jqm:d.jqModal,closeoverlay:j,modal:d.modal});if(!j){b(".jqmOverlay").click(function(){if(!f()){return false}hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose});return false})}if(N){N(b("#"+x))}b(".fm-button","#"+h.themodal).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});b("#sData","#"+t+"_2").click(function(V){D={};Q={};b("#FormError","#"+t).hide();T();if(D.id=="_empty"){i()}else{if(d.checkOnSubmit===true){G=b.extend({},D,Q);A=J(G,a._savedData);if(A){b("#"+x).data("disabled",true);b(".confirm","#"+h.themodal).show()}else{i()}}else{i()}}return false});b("#cData","#"+t+"_2").click(function(V){if(!f()){return false}hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose});return false});b("#nData","#"+t+"_2").click(function(V){if(!f()){return false}b("#FormError","#"+t).hide();var W=U();W[0]=parseInt(W[0]);if(W[0]!=-1&&W[1][W[0]+1]){if(b.isFunction(d.onclickPgButtons)){d.onclickPgButtons("next",b("#"+x),W[1][W[0]])}m(W[1][W[0]+1],e,x);b(e).setSelection(W[1][W[0]+1]);if(b.isFunction(d.afterclickPgButtons)){d.afterclickPgButtons("next",b("#"+x),W[1][W[0]+1])}K(W[0]+1,W[1].length-1)}return false});b("#pData","#"+t+"_2").click(function(W){if(!f()){return false}b("#FormError","#"+t).hide();var V=U();if(V[0]!=-1&&V[1][V[0]-1]){if(b.isFunction(d.onclickPgButtons)){d.onclickPgButtons("prev",b("#"+x),V[1][V[0]])}m(V[1][V[0]-1],e,x);b(e).setSelection(V[1][V[0]-1]);if(b.isFunction(d.afterclickPgButtons)){d.afterclickPgButtons("prev",b("#"+x),V[1][V[0]-1])}K(V[0]-1,V[1].length-1)}return false})}var R=U();K(R[0],R[1].length-1);function K(W,X,V){if(W==0){b("#pData","#"+t+"_2").addClass("ui-state-disabled")}else{b("#pData","#"+t+"_2").removeClass("ui-state-disabled")}if(W==X){b("#nData","#"+t+"_2").addClass("ui-state-disabled")}else{b("#nData","#"+t+"_2").removeClass("ui-state-disabled")}}function U(){var W=b(e).getDataIDs(),V=b("#id_g","#"+t).val(),X=b.inArray(V,W);return[X,W]}function f(){var V=true;b("#FormError","#"+t).hide();if(a.checkOnUpdate){D={};Q={};T();G=b.extend({},D,Q);A=J(G,a._savedData);if(A){b("#"+x).data("disabled",true);b(".confirm","#"+h.themodal).show();V=false}}return V}function T(){b(".FormElement","#"+t).each(function(W){switch(b(this).get(0).type){case"checkbox":if(b(this).attr("checked")){D[this.name]=b(this).val()}else{var V=b(this).attr("offval");D[this.name]=V;Q[this.name]=V}break;case"select-one":D[this.name]=b("option:selected",this).val();Q[this.name]=b("option:selected",this).text();break;case"select-multiple":D[this.name]=b(this).val();if(D[this.name]){D[this.name]=D[this.name].join(",")}else{D[this.name]=""}var X=[];b("option:selected",this).each(function(Y,Z){X[Y]=b(Z).text()});Q[this.name]=X.join(",");break;case"password":case"text":case"textarea":case"button":D[this.name]=b(this).val();D[this.name]=!e.p.autoencode?D[this.name]:b.jgrid.htmlEncode(D[this.name]);break}});return true}function r(Y,ae,ab,aj){var V,W,ag,ah=0,al,am,af,ak=[],ac=false,ad,X,Z="  ",aa="";for(var ai=1;ai<=aj;ai++){aa+=Z}if(Y!="_empty"){ac=b(ae).getInd(Y)}b(ae.p.colModel).each(function(aq){V=this.name;if(this.editrules&&this.editrules.edithidden==true){W=false}else{W=this.hidden===true?true:false}am=W?"style='display:none'":"";if(V!=="cb"&&V!=="subgrid"&&this.editable===true&&V!=="rn"){if(ac===false){al=""}else{if(V==ae.p.ExpandColumn&&ae.p.treeGrid===true){al=b("td:eq("+aq+")",ae.rows[ac]).text()}else{try{al=b.unformat(b("td:eq("+aq+")",ae.rows[ac]),{colModel:this},aq)}catch(ao){al=b("td:eq("+aq+")",ae.rows[ac]).html()}}}var ap=b.extend({},this.editoptions||{},{id:V,name:V});frmopt=b.extend({},{elmprefix:"",elmsuffix:"",rowabove:false,rowcontent:""},this.formoptions||{}),ad=parseInt(frmopt.rowpos)||ah+1,X=parseInt((parseInt(frmopt.colpos)||1)*2);if(Y=="_empty"&&ap.defaultValue){al=b.isFunction(ap.defaultValue)?ap.defaultValue():ap.defaultValue}if(!this.edittype){this.edittype="text"}af=createEl(this.edittype,ap,al);if(al==""&&this.edittype=="checkbox"){al=b(af).attr("offval")}if(a.checkOnSubmit||a.checkOnUpdate){a._savedData[V]=al}b(af).addClass("FormElement");ag=b(ab).find("tr[rowpos="+ad+"]");if(frmopt.rowabove){var ar=b(""+frmopt.rowcontent+"");b(ab).append(ar);ar[0].rp=ad}if(ag.length==0){ag=b("").addClass("FormData").attr("id","tr_"+V);b(ag).append(aa);b(ab).append(ag);ag[0].rp=ad}b("td:eq("+(X-2)+")",ag[0]).html(typeof frmopt.label==="undefined"?ae.p.colNames[aq]:frmopt.label);b("td:eq("+(X-1)+")",ag[0]).append(frmopt.elmprefix).append(af).append(frmopt.elmsuffix);ak[ah]=aq;ah++}});if(ah>0){var an=b("");an[0].rp=ah+999;b(ab).append(an);if(a.checkOnSubmit||a.checkOnUpdate){a._savedData.id=Y}}return ak}function m(W,ab,X){var af,ad,Y=0,ac,aa,V,Z,ae;if(a.checkOnSubmit||a.checkOnUpdate){a._savedData={};a._savedData.id=W}if(W=="_empty"){b(ab.p.colModel).each(function(ag){af=this.name.replace(".","\\.");V=b.extend({},this.editoptions||{});aa=b("#"+af,"#"+X);if(aa[0]!=null){Z="";if(V.defaultValue){Z=b.isFunction(V.defaultValue)?V.defaultValue():V.defaultValue;if(aa[0].type=="checkbox"){ae=Z.toLowerCase();if(ae.search(/(false|0|no|off|undefined)/i)<0&&ae!==""){aa[0].checked=true;aa[0].defaultChecked=true;aa[0].value=Z}else{aa.attr({checked:"",defaultChecked:""})}}else{aa.val(Z)}}else{if(aa[0].type=="checkbox"){aa[0].checked=false;aa[0].defaultChecked=false;Z=b(aa).attr("offval")}else{aa.val(Z)}}if(a.checkOnSubmit===true||a.checkOnUpdate){a._savedData[af]=Z}}});b("#id_g","#"+X).val("_empty");return}b("table:first tr#"+W+" td",ab.grid.bDiv).each(function(ah){af=ab.p.colModel[ah].name.replace(".","\\.");if(ab.p.colModel[ah].editrules&&ab.p.colModel[ah].editrules.edithidden===true){ad=false}else{ad=ab.p.colModel[ah].hidden===true?true:false}if(af!=="cb"&&af!=="subgrid"&&ab.p.colModel[ah].editable===true){if(af==ab.p.ExpandColumn&&ab.p.treeGrid===true){ac=b(this).text()}else{try{ac=b.unformat(this,{colModel:ab.p.colModel[ah]},ah)}catch(ag){ac=b(this).html()}}if(a.checkOnSubmit===true||a.checkOnUpdate){a._savedData[af]=ac}switch(ab.p.colModel[ah].edittype){case"password":case"text":case"button":case"image":ac=b.jgrid.htmlDecode(ac);b("#"+af,"#"+X).val(ac);break;case"textarea":if(ac==" "||ac==" "||(ac.length==1&&ac.charCodeAt(0)==160)){ac=""}b("#"+af,"#"+X).val(ac);break;case"select":ac=b.jgrid.htmlDecode(ac);b("#"+af+" option","#"+X).each(function(ai){if(!ab.p.colModel[ah].editoptions.multiple&&ac==b(this).text()){this.selected=true}else{if(ab.p.colModel[ah].editoptions.multiple){if(b.inArray(b(this).text(),ac.split(","))>-1){this.selected=true}else{this.selected=false}}else{this.selected=false}}});break;case"checkbox":ac=ac.toLowerCase();if(ac.search(/(false|0|no|off|undefined)/i)<0&&ac!==""){b("#"+af,"#"+X).attr("checked",true);b("#"+af,"#"+X).attr("defaultChecked",true)}else{b("#"+af,"#"+X).attr("checked",false);b("#"+af,"#"+X).attr("defaultChecked","")}break}Y++}});if(Y>0){b("#id_g","#"+t).val(W)}return Y}function i(){var Y,W=[true,"",""],V={};for(var X in D){W=checkValues(D[X],X,e);if(W[0]==false){break}}if(W[0]){if(b.isFunction(a.onclickSubmit)){V=a.onclickSubmit(a,D)||{}}if(b.isFunction(a.beforeSubmit)){W=a.beforeSubmit(D,b("#"+x))}}u=a.url?a.url:e.p.editurl;if(W[0]){if(!u){W[0]=false;W[1]+=" "+b.jgrid.errors.nourl}}if(W[0]===false){b("#FormError>td","#"+t).html(W[1]);b("#FormError","#"+t).show();return}if(!d.processing){d.processing=true;b("#sData","#"+t+"_2").addClass("ui-state-active");D.oper=D.id=="_empty"?"add":"edit";D=b.extend(D,a.editData,V);b.ajax({url:u,type:a.mtype,data:D,complete:function(aa,Z){if(Z!="success"){W[0]=false;W[1]=Z+" Status: "+aa.statusText+" Error code: "+aa.status}else{if(b.isFunction(a.afterSubmit)){W=a.afterSubmit(aa,D)}}if(W[0]===false){b("#FormError>td","#"+t).html(W[1]);b("#FormError","#"+t).show()}else{D=b.extend(D,Q);if(D.id=="_empty"){if(!W[2]){W[2]=parseInt(e.p.records)+1}D.id=W[2];if(a.closeAfterAdd){if(a.reloadAfterSubmit){b(e).trigger("reloadGrid")}else{b(e).addRowData(W[2],D,d.addedrow);b(e).setSelection(W[2])}hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose})}else{if(a.clearAfterAdd){if(a.reloadAfterSubmit){b(e).trigger("reloadGrid")}else{b(e).addRowData(W[2],D,d.addedrow)}m("_empty",e,x)}else{if(a.reloadAfterSubmit){b(e).trigger("reloadGrid")}else{b(e).addRowData(W[2],D,d.addedrow)}}}}else{if(a.reloadAfterSubmit){b(e).trigger("reloadGrid");if(!a.closeAfterEdit){b(e).setSelection(D.id)}}else{if(e.p.treeGrid===true){b(e).setTreeRow(D.id,D)}else{b(e).setRowData(D.id,D)}}if(a.closeAfterEdit){hideModal("#"+h.themodal,{gb:"#gbox_"+B,jqm:d.jqModal,onClose:a.onClose})}}if(b.isFunction(a.afterComplete)){Y=aa;setTimeout(function(){a.afterComplete(Y,D,b("#"+x));Y=null},500)}}d.processing=false;if(a.checkOnSubmit||a.checkOnUpdate){b("#"+x).data("disabled",false);if(a._savedData.id!="_empty"){a._savedData=D}}b("#sData","#"+t+"_2").removeClass("ui-state-active");try{b(":input:visible","#"+x)[0].focus()}catch(ab){}},error:function(ab,Z,aa){b("#FormError>td","#"+t).html(Z+" : "+aa);b("#FormError","#"+t).show();d.processing=false;b("#"+x).data("disabled",false);b("#sData","#"+t+"_2").removeClass("ui-state-active")}})}}function J(Y,V){var W=false,X;for(X in Y){if(Y[X]!=V[X]){W=true;break}}return W}})},viewGridRow:function(c,d){d=b.extend({top:0,left:0,width:0,height:"auto",dataheight:"auto",modal:false,drag:true,resize:true,jqModal:true,closeOnEscape:false,labelswidth:"30%",closeicon:[],navkeys:[false,38,40],onClose:null},b.jgrid.view,d||{});return this.each(function(){var w=this;if(!w.grid||!c){return}if(!d.imgpath){d.imgpath=w.p.imgpath}var q=w.p.id,y="ViewGrid_"+q,r="ViewTbl_"+q,i={themodal:"viewmod"+q,modalhead:"viewhd"+q,modalcontent:"viewcnt"+q,scrollelm:y},g=1,e=0;if(b("#"+i.themodal).html()!=null){b(".ui-jqdialog-title","#"+i.modalhead).html(d.caption);b("#FormError","#"+r).hide();l(c,w);viewModal("#"+i.themodal,{gbox:"#gbox_"+q,jqm:d.jqModal,jqM:false,modal:d.modal});j()}else{b(w.p.colModel).each(function(C){var D=this.formoptions;g=Math.max(g,D?D.colpos||0:0);e=Math.max(e,D?D.rowpos||0:0)});var x=isNaN(d.dataheight)?d.dataheight:d.dataheight+"px";var v,A=b("
"),k=b("
");b(A).append(k);var u=m(c,w,k,g),s="",t="",B=""+d.bClose+"";if(e>0){var f=[];b.each(b(k)[0].rows,function(C,D){f[C]=D});f.sort(function(D,C){if(D.rp>C.rp){return 1}if(D.rp").append(A).append("
"+B+"
");createModal(i,z,d,"#gview_"+w.p.id,b("#gview_"+w.p.id)[0]);z=null;jQuery("#"+i.themodal).keydown(function(C){if(C.which===27){if(p){hideModal(this,{gb:d.gbox,jqm:d.jqModal,onClose:d.onClose})}return false}if(d.navkeys[0]===true){if(C.which===d.navkeys[1]){b("#pData","#"+r+"_2").trigger("click");return false}if(C.which===d.navkeys[2]){b("#nData","#"+r+"_2").trigger("click");return false}}});d.closeicon=b.extend([true,"left","ui-icon-close"],d.closeicon);if(d.closeicon[0]==true){b("#cData","#"+r+"_2").addClass(d.closeicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("")}viewModal("#"+i.themodal,{gbox:"#gbox_"+q,jqm:d.jqModal,modal:d.modal});b(".fm-button:not(.ui-state-disabled)","#"+r+"_2").hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});j();b("#cData","#"+r+"_2").click(function(C){hideModal("#"+i.themodal,{gb:"#gbox_"+q,jqm:d.jqModal,onClose:d.onClose});return false});b("#nData","#"+r+"_2").click(function(C){b("#FormError","#"+r).hide();var D=h();D[0]=parseInt(D[0]);if(D[0]!=-1&&D[1][D[0]+1]){if(b.isFunction(d.onclickPgButtons)){d.onclickPgButtons("next",b("#"+y),D[1][D[0]])}l(D[1][D[0]+1],w);b(w).setSelection(D[1][D[0]+1]);if(b.isFunction(d.afterclickPgButtons)){d.afterclickPgButtons("next",b("#"+y),D[1][D[0]+1])}n(D[0]+1,D[1].length-1)}j();return false});b("#pData","#"+r+"_2").click(function(D){b("#FormError","#"+r).hide();var C=h();if(C[0]!=-1&&C[1][C[0]-1]){if(b.isFunction(d.onclickPgButtons)){d.onclickPgButtons("prev",b("#"+y),C[1][C[0]])}l(C[1][C[0]-1],w);b(w).setSelection(C[1][C[0]-1]);if(b.isFunction(d.afterclickPgButtons)){d.afterclickPgButtons("prev",b("#"+y),C[1][C[0]-1])}n(C[0]-1,C[1].length-1)}j();return false})}function j(){if(d.closeOnEscape===true||d.navkeys[0]===true){setTimeout(function(){b(".ui-jqdialog-titlebar-close","#"+i.modalhead).focus()},0)}}var o=h();n(o[0],o[1].length-1);function n(D,E,C){if(D==0){b("#pData","#"+r+"_2").addClass("ui-state-disabled")}else{b("#pData","#"+r+"_2").removeClass("ui-state-disabled")}if(D==E){b("#nData","#"+r+"_2").addClass("ui-state-disabled")}else{b("#nData","#"+r+"_2").removeClass("ui-state-disabled")}}function h(){var D=b(w).getDataIDs(),C=b("#id_g","#"+r).val(),E=b.inArray(C,D);return[E,D]}function m(I,O,M,U){var E,H,P,X,C,S=0,W,Y,V=[],N=false,K="  ",L="",F="  ",J=["integer","number","currency"],R=0,Q=0,G,D;for(var T=1;T<=U;T++){L+=T==1?K:F}b(O.p.colModel).each(function(aa){if(this.editrules&&this.editrules.edithidden===true){H=false}else{H=this.hidden===true?true:false}if(!H&&this.align==="right"){if(this.formatter&&b.inArray(this.formatter,J)!==-1){R=Math.max(R,parseInt(this.width,10))}else{Q=Math.max(Q,parseInt(this.width,10))}}});G=R!==0?R:Q!==0?Q:0;N=b(O).getInd(I);b(O.p.colModel).each(function(ab){E=this.name;D=false;if(this.editrules&&this.editrules.edithidden===true){H=false}else{H=this.hidden===true?true:false}Y=H?"style='display:none'":"";if(E!=="cb"&&E!=="subgrid"&&this.editable===true){if(N===false){W=""}else{if(E==O.p.ExpandColumn&&O.p.treeGrid===true){W=b("td:eq("+ab+")",O.rows[N]).text()}else{W=b("td:eq("+ab+")",O.rows[N]).html()}}D=this.align==="right"&&G!==0?true:false;var aa=b.extend({},this.editoptions||{},{id:E,name:E}),af=b.extend({},{rowabove:false,rowcontent:""},this.formoptions||{}),ac=parseInt(af.rowpos)||S+1,ae=parseInt((parseInt(af.colpos)||1)*2);if(af.rowabove){var ad=b(""+af.rowcontent+"");b(M).append(ad);ad[0].rp=ac}P=b(M).find("tr[rowpos="+ac+"]");if(P.length==0){P=b("").addClass("FormData").attr("id","trv_"+E);b(P).append(L);b(M).append(P);P[0].rp=ac}b("td:eq("+(ae-2)+")",P[0]).html(""+(typeof af.label==="undefined"?O.p.colNames[ab]:af.label)+"");b("td:eq("+(ae-1)+")",P[0]).append(""+W+"").attr("id","v_"+E);if(D){b("td:eq("+(ae-1)+") span",P[0]).css({"text-align":"right",width:G+"px"})}V[S]=ab;S++}});if(S>0){var Z=b("");Z[0].rp=S+99;b(M).append(Z)}return V}function l(G,H){var C,I,F=0,E,D;b("#"+G+" td",H.grid.bDiv).each(function(J){C=H.p.colModel[J].name.replace(".","\\.");if(H.p.colModel[J].editrules&&H.p.colModel[J].editrules.edithidden===true){I=false}else{I=H.p.colModel[J].hidden===true?true:false}if(C!=="cb"&&C!=="subgrid"&&H.p.colModel[J].editable===true){if(C==H.p.ExpandColumn&&H.p.treeGrid===true){E=b(this).text()}else{E=b(this).html()}D=b.extend({},H.p.colModel[J].editoptions||{});C="v_"+C;b("#"+C+" span","#"+r).html(E);if(I){b("#"+C,"#"+r).parents("tr:first").hide()}F++}});if(F>0){b("#id_g","#"+r).val(G)}return F}})},delGridRow:function(c,d){d=b.extend({top:0,left:0,width:240,height:"auto",dataheight:"auto",modal:false,drag:true,resize:true,url:"",mtype:"POST",reloadAfterSubmit:true,beforeShowForm:null,afterShowForm:null,beforeSubmit:null,onclickSubmit:null,afterSubmit:null,jqModal:true,closeOnEscape:false,delData:{},delicon:[],cancelicon:[],onClose:null},b.jgrid.del,d||{});a=d;return this.each(function(){var l=this;if(!l.grid){return}if(!c){return}var m=typeof d.beforeShowForm==="function"?true:false,g=typeof d.afterShowForm==="function"?true:false,e=l.p.id,f={},j="DelTbl_"+e,h={themodal:"delmod"+e,modalhead:"delhd"+e,modalcontent:"delcnt"+e,scrollelm:j};if(isArray(c)){c=c.join()}if(b("#"+h.themodal).html()!=null){b("#DelData>td","#"+j).text(c);b("#DelError","#"+j).hide();if(d.processing===true){d.processing=false;b("#dData","#"+j).removeClass("ui-state-active")}if(m){d.beforeShowForm(b("#"+j))}viewModal("#"+h.themodal,{gbox:"#gbox_"+e,jqm:d.jqModal,jqM:false,modal:d.modal});if(g){d.afterShowForm(b("#"+j))}}else{var n=isNaN(d.dataheight)?d.dataheight:d.dataheight+"px";var k="
";k+="";k+="";k+="";k+='";k+="
'+d.msg+"
 
";var i=""+d.bSubmit+"",o=""+d.bCancel+"";k+="
"+i+" "+o+"
";d.gbox="#gbox_"+e;createModal(h,k,d,"#gview_"+l.p.id,b("#gview_"+l.p.id)[0]);b(".fm-button","#"+j+"_2").hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});d.delicon=b.extend([true,"left","ui-icon-scissors"],d.delicon);d.cancelicon=b.extend([true,"left","ui-icon-cancel"],d.cancelicon);if(d.delicon[0]==true){b("#dData","#"+j+"_2").addClass(d.delicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("")}if(d.cancelicon[0]==true){b("#eData","#"+j+"_2").addClass(d.cancelicon[1]=="right"?"fm-button-icon-right":"fm-button-icon-left").append("")}b("#dData","#"+j+"_2").click(function(s){var q=[true,""];f={};var r=b("#DelData>td","#"+j).text();if(typeof d.onclickSubmit==="function"){f=d.onclickSubmit(a)||{}}if(typeof d.beforeSubmit==="function"){q=d.beforeSubmit(r)}var p=a.url?a.url:l.p.editurl;if(!p){q[0]=false;q[1]+=" "+b.jgrid.errors.nourl}if(q[0]===false){b("#DelError>td","#"+j).html(q[1]);b("#DelError","#"+j).show()}else{if(!d.processing){d.processing=true;b(this).addClass("ui-state-active");var t=b.extend({oper:"del",id:r},d.delData,f);b.ajax({url:p,type:d.mtype,data:t,complete:function(x,v){if(v!="success"){q[0]=false;q[1]=v+" Status: "+x.statusText+" Error code: "+x.status}else{if(typeof a.afterSubmit==="function"){q=a.afterSubmit(x,r)}}if(q[0]===false){b("#DelError>td","#"+j).html(q[1]);b("#DelError","#"+j).show()}else{if(a.reloadAfterSubmit){if(l.p.treeGrid){b(l).setGridParam({treeANode:0,datatype:l.p.treedatatype})}b(l).trigger("reloadGrid")}else{var u=[];u=r.split(",");if(l.p.treeGrid===true){try{b(l).delTreeNode(u[0])}catch(y){}}else{for(var w=0;wtd","#"+j).html(u+" : "+v);b("#DelError","#"+j).show();d.processing=false;b("#dData","#"+j+"_2").removeClass("ui-state-active")}})}}return false});b("#eData","#"+j+"_2").click(function(p){hideModal("#"+h.themodal,{gb:"#gbox_"+e,jqm:d.jqModal,onClose:a.onClose});return false});if(m){d.beforeShowForm(b("#"+j))}viewModal("#"+h.themodal,{gbox:"#gbox_"+e,jqm:d.jqModal,modal:d.modal});if(g){d.afterShowForm(b("#"+j))}}if(d.closeOnEscape===true){setTimeout(function(){b(".ui-jqdialog-titlebar-close","#"+h.modalhead).focus()},0)}})},navGrid:function(f,h,e,g,d,c,i){h=b.extend({edit:true,editicon:"ui-icon-pencil",add:true,addicon:"ui-icon-plus",del:true,delicon:"ui-icon-trash",search:true,searchicon:"ui-icon-search",refresh:true,refreshicon:"ui-icon-refresh",refreshstate:"firstpage",view:false,viewicon:"ui-icon-document",position:"left",closeOnEscape:true},b.jgrid.nav,h||{});return this.each(function(){var j={themodal:"alertmod",modalhead:"alerthd",modalcontent:"alertcnt"},n=this,m,s,o,k;if(!n.grid){return}if(b("#"+j.themodal).html()==null){if(typeof window.innerWidth!="undefined"){m=window.innerWidth,s=window.innerHeight}else{if(typeof document.documentElement!="undefined"&&typeof document.documentElement.clientWidth!="undefined"&&document.documentElement.clientWidth!=0){m=document.documentElement.clientWidth,s=document.documentElement.clientHeight}else{m=1024;s=768}}createModal(j,"
"+h.alerttext+"
",{gbox:"#gbox_"+n.p.id,jqModal:true,drag:true,resize:true,caption:h.alertcap,top:s/2-25,left:m/2-100,width:200,height:"auto",closeOnEscape:h.closeOnEscape},"","",true)}var p,q=b(""),r="",l=b(n.p.pager).attr("id")||"pager";if(h.add){g=g||{};p=b("");b(p).append("
"+h.addtext+"
");b("tr",q).append(p);b(p,q).attr({title:h.addtitle||"",id:g.id||"add_"+n.p.id}).click(function(){if(typeof h.addfunc=="function"){h.addfunc()}else{b(n).editGridRow("new",g)}return false}).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});p=null}if(h.edit){p=b("");e=e||{};b(p).append("
"+h.edittext+"
");b("tr",q).append(p);b(p,q).attr({title:h.edittitle||"",id:e.id||"edit_"+n.p.id}).click(function(){var t=n.p.selrow;if(t){if(typeof h.editfunc=="function"){h.editfunc(t)}else{b(n).editGridRow(t,e)}}else{viewModal("#"+j.themodal,{gbox:"#gbox_"+n.p.id,jqm:true});b("#jqg_alrt").focus()}return false}).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});p=null}if(h.view){p=b("");i=i||{};b(p).append("
"+h.viewtext+"
");b("tr",q).append(p);b(p,q).attr({title:h.viewtitle||"",id:i.id||"view_"+n.p.id}).click(function(){var t=n.p.selrow;if(t){b(n).viewGridRow(t,i)}else{viewModal("#"+j.themodal,{gbox:"#gbox_"+n.p.id,jqm:true});b("#jqg_alrt").focus()}return false}).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});p=null}if(h.del){p=b("");d=d||{};b(p).append("
"+h.deltext+"
");b("tr",q).append(p);b(p,q).attr({title:h.deltitle||"",id:d.id||"del_"+n.p.id}).click(function(){var t;if(n.p.multiselect){t=n.p.selarrrow;if(t.length==0){t=null}}else{t=n.p.selrow}if(t){b(n).delGridRow(t,d)}else{viewModal("#"+j.themodal,{gbox:"#gbox_"+n.p.id,jqm:true});b("#jqg_alrt").focus()}return false}).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});p=null}if(h.add||h.edit||h.del||h.view){b("tr",q).append(r)}if(h.search){p=b("");c=c||{};b(p).append("
"+h.searchtext+"
");b("tr",q).append(p);b(p,q).attr({title:h.searchtitle||"",id:c.id||"search_"+n.p.id}).click(function(){b(n).searchGrid(c);return false}).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});p=null}if(h.refresh){p=b("");b(p).append("
"+h.refreshtext+"
");b("tr",q).append(p);b(p,q).attr({title:h.refreshtitle||"",id:"refresh_"+n.p.id}).click(function(){n.p.search=false;try{var u=n.p.id;b("#fbox_"+u).searchFilter().reset()}catch(v){}switch(h.refreshstate){case"firstpage":n.p.page=1;b(n).trigger("reloadGrid");break;case"current":var t=n.p.multiselect===true?n.p.selarrrow:n.p.selrow;b(n).trigger("reloadGrid");setTimeout(function(){if(n.p.multiselect===true){if(t.length>0){for(var w=0;w