diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..c5e009f --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "nterchange/assets/components" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..00eba56 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +## 4.0.0 + +* Reboot repository. Remove old SQL, passwords, deprecated files + +## 3.3.0 + +* Many changes to support grids +* NModel now implements `Array Access` +* Update bootstrap to 3.1 +* Test suite refactored + +## 3.2.0 + +* Update to CKEditor 4.0 +* Config class with static properties added as an alternative to constants for configuration + +## 3.1.22 + +* NAsset - dynamic coffeescript & less compiling +* NModel::$_delete_uploads flag on model to delete upload files along with records diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dd5d59f --- /dev/null +++ b/LICENSE @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2006 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + +This software consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see . + +PHP includes the Zend Engine, freely available at +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..32f5bb6 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +nterchange +========== + +Content Management that gets out of your way + +Install +------- + +This repository contains the core libraries, intended for use by a front-end. + +Testing +------- + +Set up the dependencies for the backend if necessary, then run PHPUnit. +This will clear and create a database called `nterchange_test`. + + composer install + vendor/bin/phpunit -c test/phpunit.xml diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..4452b00 --- /dev/null +++ b/Rakefile @@ -0,0 +1,50 @@ +require 'rubygems' +require 'bundler' +require 'pathname' +require 'logger' +require 'fileutils' +Bundler.require + +require 'active_record' +require 'yui/compressor' +require 'uglifier' + +dir = Rake.application.original_dir + +ROOT = Pathname(File.dirname(__FILE__)) +BUNDLES = %w( surftoedit.js surftoedit.css jquery-ui.js noty.js ) +BUILD_DIR = ROOT.join("nterchange/assets") +SOURCE_DIR = ROOT.join("app/assets") +COMPONENTS_DIR = ROOT.join("nterchange/assets/components") + +namespace :asset do + desc "Compile all assets" + task :compile do + Sprockets::Cache::FileStore.new(File.join ROOT, '.cache') + sprockets = Sprockets::Environment.new(ROOT) do |env| + env.logger = Logger.new(STDOUT) + end + + # sprockets.css_compressor = YUI::CssCompressor.new + # sprockets.js_compressor = Uglifier.new(:mangle => false) + + sprockets.append_path(SOURCE_DIR.join('javascripts').to_s) + sprockets.append_path(SOURCE_DIR.join('stylesheets').to_s) + sprockets.append_path(COMPONENTS_DIR.to_s) + + BUNDLES.each do |bundle| + asset = sprockets.find_asset(bundle) + prefix, basename = asset.pathname.to_s.split('/')[-2..-1] + FileUtils.mkpath BUILD_DIR.join(prefix) + realname = asset.pathname.basename.to_s.split(".")[0..1].join(".") + output_file = BUILD_DIR.join(prefix, realname) + + File.open(output_file, 'wb') do |f| + f.write asset.to_s + end + end + + # # Make available bower components in public_html + # FileUtils.cp_r(Dir["#{SOURCE_DIR}/components*"],BUILD_DIR) + end +end diff --git a/app/controllers/admin_controller.php b/app/controllers/admin_controller.php new file mode 100644 index 0000000..618e303 --- /dev/null +++ b/app/controllers/admin_controller.php @@ -0,0 +1,68 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class AdminController extends nterchangeController { + var $user_level_required = N_USER_ROOT; + var $name = 'admin'; + + function __construct() { + parent::__construct(); + } + + function index() { + $this->redirectTo(array('users', 'viewlist')); + } + + function viewlist($parameter) { + $this->loadSubnav($parameter); + parent::viewlist($parameter); + } + function create($parameter) { + $this->loadSubnav($parameter); + parent::create($parameter); + } + function edit($parameter) { + $this->loadSubnav($parameter); + parent::edit($parameter); + } + + function &getDefaultModel() { + return false; + } + + function loadSidebar($parameter) { + $this->set('SIDEBAR_TITLE', $this->page_title . ' Info'); + $this->setAppend('SIDEBAR_CONTENT', $this->render(array('action'=>'sidebar_content', 'return'=>true))); + } + + function loadSubnav($parameter) { + $subnav = array(); + $subnav[] = array('title'=>'Users', 'controller'=>'users', 'action'=>'viewlist', 'id'=>$parameter, 'class'=>''); + $subnav[] = array('title'=>'Audit Trail', 'controller'=>'audit_trail', 'action'=>'viewlist', 'id'=>$parameter, 'class'=>''); + $subnav[] = array('title'=>'Templates', 'controller'=>'page_template', 'action'=>'viewlist', 'id'=>$parameter, 'class'=>''); + $subnav[] = array('title'=>'Assets', 'controller'=>'cms_asset_info', 'action'=>'viewlist', 'id'=>$parameter, 'class'=>''); + foreach ($subnav as $k=>$nav) { + if ($nav['controller'] == $this->name && $nav['action'] == $this->action) { + $subnav[$k]['class'] = 'current'; + } + } + $this->set('subnav', $subnav); + } +} +?> diff --git a/app/controllers/asset_controller.php b/app/controllers/asset_controller.php new file mode 100644 index 0000000..32effac --- /dev/null +++ b/app/controllers/asset_controller.php @@ -0,0 +1,289 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class AssetController extends ContentController { + var $paging = 25; + var $search_field = 'cms_headline'; + + function __construct() { + parent::__construct(); + } + + function index($parameter) { + $this->redirectTo('viewlist', $parameter); + } + + private function get_search_field(&$model, &$options){ + // Search $model->search_field to limit shown assets. + + $search = isset($_GET['search']) ? $_GET['search'] : false; + $options['is_search'] = $search ? true : false; + + if (!$options['is_search']) return; + + $search_field = isset($_GET['search_field']) ? $_GET['search_field'] : null; + if (!$search_field){ + $search_field = isset($model->search_field) ? $model->search_field : $this->search_field; + } + + if ($options['is_search'] && $search_field){ + $this->set('search_field', Inflector::humanize($search_field)); + $options['conditions'] = "$search_field LIKE '%$search%'"; + } + } + + function viewlist($parameter=null, $layout=true, $model=false) { + $options = array(); + $assigns = array(); + $model = $model ? $model : $this->getDefaultModel(); + $this->page_title = Inflector::humanize($this->name); + $this->auto_render = false; + $this->base_dir = APP_DIR; + $assigns['TITLE'] = Inflector::humanize($this->name); + + if ($model){ + $this->get_search_field($model, $options); + $this->get_viewlist_options($model, $options); + $this->get_sort_options($model, $options); + $this->set_pagination($model); + + $model->find($options); + + $page_content = &NController::singleton('page_content'); + $page_content_model = &$page_content->getDefaultModel(); + + $pk = $model->primaryKey(); + $models = array(); + $headline = $model->getHeadline()?$model->getHeadline():'cms_headline'; + $i = 0; + while ($model->fetch()) { + $arr = $model->toArray(); + $arr['_headline'] = isset($arr['cms_headline']) && $arr['cms_headline']?$arr['cms_headline']:$model->makeHeadline(); + $arr['_remove_delete'] = $page_content_model->isWorkflowContent($this->name, $arr[$pk])?1:0; + // Remove delete for models that have specified this. + $arr['_remove_delete'] = (isset($model->remove_delete) && ($model->remove_delete == true))?1:0; + $models[] = $arr; + unset($arr); + } + // Override standard paging limit if chosen in the model file. + $paging = isset($model->paging)?$model->paging:$this->paging; + // If paging is not disabled in the model AND the records are > than the paging size AND not searching. + if (($paging > 0) && count($models) > $paging && !$options['is_search']) { + SmartyPaginate::connect($this->name); + SmartyPaginate::setLimit($paging, $this->name); + SmartyPaginate::setTotal(count($models), $this->name); + $view = &NView::singleton($this); + SmartyPaginate::assign($view, 'paginate', $this->name); + // TODO: Could be more efficient and only get records it needs to. + $models = array_slice($models, SmartyPaginate::getCurrentIndex($this->name), SmartyPaginate::getLimit($this->name)); + $this->set('paging', true); + } + $this->set(array('rows'=>$models, 'asset'=>$this->name, 'asset_name'=>$this->page_title)); + unset($models); + } + $this->render(array('layout'=>'default')); + } + + private function get_viewlist_options(&$model, &$options){ + // Can set options in the model about items displayed in the viewlist. + // Only show items that meet a certain criteria - not everything in the list. + // For example: $this->viewlist_options = array('conditions'=>"cms_modified_by_user = '4'"); + if (isset($model->viewlist_options)) { + foreach ($model->viewlist_options as $key => $val) { + if (isset($options[$key])) { + $options[$key] .= ' AND ' . $val; + } else { + $options[$key] = "$val"; + } + } + } + + if (isset($model->viewlist_fields)) { + $this->set('viewlist_fields', $model->viewlist_fields); + } + + } + + private function get_sort_options(&$model, &$options){ + $sort_by = isset($_GET['sort'])?$_GET['sort']:null; + if($sort_by) { + $sort_by_array = explode('_', $sort_by); + $sort_order = $sort_by_array[count($sort_by_array)-1]; + $sort_array = array(); + if(strtolower($sort_order) == 'asc') { + $sort_field = str_replace('_asc', '', $sort_by); + $sort_array['field'] = $sort_field; + $sort_array['arrow_asc'] = true; + $sort_array['link'] = $sort_field.'_desc'; + $options['order_by'] = $sort_field. ' ASC'; + } else { + $sort_field = str_replace('_desc', '', $sort_by); + $sort_array['field'] = $sort_field; + $sort_array['arrow_desc'] = true; + $sort_array['link'] = $sort_field.'_asc'; + $options['order_by'] = $sort_field. ' DESC'; + } + $this->set('sort_array', $sort_array); + } + } + + private function set_pagination(&$model){ + + } + + function show($parameter) { + $this->page_title = Inflector::humanize($this->name); + $this->loadSidebar($parameter); + parent::show($parameter); + } + + function edit($parameter) { + $this->page_title = Inflector::humanize($this->name); + $this->loadSidebar($parameter); + parent::edit($parameter); + } + + function create($parameter=null) { + $this->page_title = Inflector::humanize($this->name); + parent::create($parameter); + } + + function delete($parameter) { + $this->page_title = Inflector::humanize($this->name); + if (SITE_WORKFLOW) { + // need to test for workflow first + $pc_model = &NModel::factory('page_content'); + /* @var $pc_model PageContent */ + $in_workflow = $pc_model->isWorkflowContent($this->name, $parameter); + // if it's in a workflowed page + if ($in_workflow) { + $this->flash->set('notice', 'The record cannot be deleted until it is removed from the workflow page it belongs to.'); + include_once 'view/helpers/url_helper.php'; + $referer = isset($this->params['_referer'])?urldecode($this->params['_referer']):false; + if ($referer) { + header('Location:' . $referer); + exit; + } + $this->redirectTo('viewlist'); + } + } + parent::delete($parameter); + } + + function loadSidebar($parameter) { + $this->set('SIDEBAR_TITLE', $this->page_title . ' Info'); + $page_model = &NModel::singleton('page'); + $page_content_model = &NModel::factory('page_content'); + $page_content_model->content_asset = $this->name; + $page_content_model->content_asset_id = $parameter; + $this->setAppend('SIDEBAR_CONTENT', $this->render(array('action'=>'sidebar_edit_status', 'return'=>true))); + if ($page_content_model->find()) { + $pages = array(); + while ($page_content_model->fetch()) { + $page_model->reset(); + if ($page_model->get($page_content_model->page_id)) { + $pages[] = $page_model->toArray(); + } + } + $page_model->reset(); + unset($page_model); + $this->set('pages', $pages); + $this->setAppend('SIDEBAR_CONTENT', $this->render(array('action'=>'sidebar_page_content', 'return'=>true))); + } + unset($page_content_model); + if ($this->versioning) { + $user_model = $this->loadModel('cms_auth'); + $model = $this->getDefaultModel(); + $model = clone($model); + if (!$model->{$model->primaryKey()}) { + $model->get($parameter); + $this->convertDateTimesToClient($model); + } + $model_data = $model->toArray(); + if ($user_model->get($model_data['cms_modified_by_user'])) { + $model_data['user'] = $user_model->toArray(); + } + $this->set($model_data); + $user_model->reset(); + $version_model = $this->loadModel('cms_nterchange_versions'); + $user_model = $this->loadModel('cms_auth'); + $version_model->asset = $this->name; + $version_model->asset_id = $parameter; + if ($version_model->find(array('order_by'=>'cms_modified DESC'))) { + $versions = array(); + while ($version_model->fetch()) { + $version = $version_model->toArray(); + if ($version_data = @unserialize($version['version'])) { + if ($user_model->get($version_data['cms_modified_by_user'])) { + $version['user'] = $user_model->toArray(); + } + $user_model->reset(); + $versions[] = $version; + } + } + $this->set('versions', $versions); + } + $this->setAppend('SIDEBAR_CONTENT', $this->render(array('action'=>'sidebar_versions', 'return'=>true))); + unset($version_model); + } + } + + /** + * Called before the form is generated + * + * Sets up a default rule for content tables so there are no + * identical headlines + * + * @see NController::preGenerateForm(); + * @access public + * @return null + */ + function preGenerateForm() { + $model = &$this->getDefaultModel(); + $fields = $model->fields(); + if (in_array('cms_headline', $fields)) { + $model->form_rules[] = array('cms_headline', 'This headline is already being used', 'callback', array(&$this, 'uniqueHeadline')); + } + parent::preGenerateForm(); + } + + function getAssetContent($params) { + $id = isset($params['id'])?$params['id']:false; + $template = isset($params['template'])?$params['template']:'default'; + $model = &$this->getDefaultModel($this->name); + if($id && $model && $model->get($id)) { + $this->set($model->toArray()); + unset($model); + return $this->render(array('action'=>$template, 'return'=>true)); + } else { + return false; + } + } + /* + * Returns a controller form for the asset + */ + function getForm(){ + require_once 'controller/form.php'; + $model = $this->getDefaultModel(); + $cform = new ControllerForm($this, $model); + return $cform->getForm(); + + } +} +?> diff --git a/app/controllers/audit_trail_controller.php b/app/controllers/audit_trail_controller.php new file mode 100644 index 0000000..e10234c --- /dev/null +++ b/app/controllers/audit_trail_controller.php @@ -0,0 +1,423 @@ + + * @author Darron Froese + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class AuditTrailController extends AdminController { + function __construct() { + $this->name = 'audit_trail'; + parent::__construct(); + } + + function index() { + $this->redirectTo('viewlist'); + } + + /** + * viewlist - Shows a list of audit trail records from the current date (by default). + * Can browse around records from different dates using the form + * at the top of the page. + * + * @return void + **/ + function viewlist($parameter) { + include_once 'n_date.php'; + include_once 'n_quickform.php'; + require_once 'HTML/QuickForm/Renderer/Array.php'; + + $this->_auth = new NAuth; + $this->auto_render = false; + + // set up the search form + $form = new NQuickForm('audit_search', 'get'); + + if ($date_params = $this->getParam('date')) { + $date = $this->dateStartEnd($date_params); + } else { + $date = $this->dateStartEnd(); + } + + $el = &$form->addElement('date', 'date', 'Date', array('addEmptyOption'=>true, 'format'=>'F d Y', 'maxYear' => 2015)); + $el->setValue($date['used']); + + $form->addElement('submit', null, 'Search'); + + $renderer = new HTML_QuickForm_Renderer_Array(true, true); + $form->accept($renderer); + $this->set('audit_search', $renderer->toArray()); + + $model = &$this->getDefaultModel(); + + if ($model->find(array('conditions'=>'cms_created BETWEEN ' . $model->quote($date['start']) . ' AND ' . $model->quote($date['end']), 'order_by'=>'cms_created DESC'))) { + $html = ''; + + if ($date['month']) { + $html .= "

Showing Monthly Results for: ".date("F, Y", strtotime($date['used'])); + } + while ($model->fetch()) { + // Actually turn the id's into something readable. + $info = $this->humanizeAuditTrailRecord($model); + $this->set($info); + $html .= $this->render(array('action'=>'audit_trail_record', 'return'=>true)); + } + + $this->set('audit_trail', $html); + $this->set('result_count', $model->numRows()); + } else { + $this->set('result_count', 'no'); + $this->set('audit_trail', '

There were no results found for the specified date.

'); + } + // Exposes an RSS feed link to Admin or higher users. + if (defined('RSS_AUDIT_TRAIL') && RSS_AUDIT_TRAIL) { + NDebug::debug('We are checking to see if we can display the RSS feed.' , N_DEBUGTYPE_INFO); + $this->checkRSSFeed(); + } + $this->set('date', $date['used']); + $this->loadSubnav($parameter); + $this->render(array('layout'=>'default')); + } + + function dateStartEnd($params=false){ + $y = (isset($params['Y']) && $params['Y']) ? $params['Y'] : false; + $m = (isset($params['F']) && $params['F']) ? $params['F'] : false; + $d = (isset($params['d']) && $params['d']) ? $params['d'] : false; + + $date = array(); + $date['month'] = false; + + if ($y && $d && $m){ + // Fully qualified + $date_arg = date('Y-m-d', strtotime("$y-$m-$d")); + $date['start'] = NDate::convertTimeToUTC($date_arg . ' 00:00:00', '%Y-%m-%d %H:%M:%S'); + $date['end'] = NDate::convertTimeToUTC($date_arg . ' 23:59:59', '%Y-%m-%d %H:%M:%S'); + $date['used'] = $date_arg; + return($date); + } + + if ((! $params['d']) && ( $y && $params['F'] )) { + // One Month + $date_arg = date('Y-m-d', strtotime("$y-$m-1")); + $days_in_month = date('t', strtotime($date_arg)); + $month_end = date('Y-m-d', strtotime("$y-$m-$days_in_month")); + $date['start'] = NDate::convertTimeToUTC($date_arg . ' 00:00:00', '%Y-%m-%d %H:%M:%S'); + $date['end'] = NDate::convertTimeToUTC($month_end . ' 23:59:59', '%Y-%m-%d %H:%M:%S'); + $date['used'] = "$y-$m-1"; + $date['month'] = true; + return $date; + } + + // Default to one day: today + $date['start'] = NDate::convertTimeToUTC(date('Y-m-d') . ' 00:00:00', '%Y-%m-%d %H:%M:%S'); + $date['end'] = NDate::convertTimeToUTC(date('Y-m-d') . ' 23:59:59', '%Y-%m-%d %H:%M:%S'); + $date['used'] = date('Y-m-d'); + return($date); + + } + + function page($parameter) { + $page_id = $parameter; + $this->_auth = new NAuth; + $this->auto_render = false; + // set up the search form + include_once 'n_quickform.php'; + // search for the date + /* @var $model cmsAuditTrail */ + $model = &$this->getDefaultModel(); + $model->page_id = $page_id; + if ($model->find(array('order_by'=>'cms_created DESC'))) { + $html = ''; + while ($model->fetch()) { + // Actually turn the id's into something readable. + $info = $this->humanizeAuditTrailRecord($model); + $this->set($info); + $html .= $this->render(array('action'=>'page_audit_trail_record', 'return'=>true)); + } + $this->set('audit_trail', $html); + $this->set('result_count', $model->numRows()); + } else { + $this->set('result_count', 'no'); + $this->set('audit_trail', '

There were no results found for the specified page.

'); + } + // Exposes an RSS feed link to Admin or higher users. + if (defined('RSS_AUDIT_TRAIL') && RSS_AUDIT_TRAIL) { + NDebug::debug('We are checking to see if we can display the RSS feed.' , N_DEBUGTYPE_INFO); + $this->checkRSSFeed(); + } + $this->loadSubnav($parameter); + $this->render(array('layout'=>'default')); + } + + /** + * humanizeAuditTrailRecord - Turns the id's in the cms_audit_trail table into human readable + * English. + * + * @param object An object which contains an audit trail record. + * @return array An array of human readable information about that audit trail record. + **/ + function humanizeAuditTrailRecord($model) { + $this->convertDateTimesToClient($model); + $info = array('user'=>false, 'asset'=>false, 'workflow'=>false, 'workflow_group'=>false, 'page'=>false, 'page_content'=>false); + if ($model->user_id) { + if ($model->user_id == $model->website_user_id) { + // This is a hack for timed_removal of content - see the cms_audit_trail model for info. + $info['user'] = array('id'=>$model->website_user_id, 'real_name'=>$model->website_user_name, 'email'=>$model->website_user_email); + } else { + $info['user'] = $this->getAuditInfo('users', $model->user_id); + } + } + if ($model->workflow_id) { + $info['workflow'] = $this->getAuditInfo('workflow', $model->workflow_id); + } + if ($model->asset && $model->asset != 'page' && $model->asset_id) { + $info['asset'] = $this->getAuditInfo($model->asset, $model->asset_id); + $info['asset_type'] = $model->asset; + $info['asset_name'] = Inflector::humanize($model->asset); + } else if ($info['workflow']) { + $info['asset'] = $this->getAuditInfo($info['workflow']['asset'], $info['workflow']['asset_id']); + $info['asset_type'] = $info['workflow']['asset']; + $info['asset_name'] = Inflector::humanize($info['workflow']['asset']); + } + if ($model->workflow_group_id) { + $info['workflow_group'] = $this->getAuditInfo('workflow_group', $model->workflow_group_id); + } else if ($info['workflow']) { + // if there's no workflow_group_id right in the audit trail, try the workflow['workflow_group_id'] + $info['workflow_group'] = $this->getAuditInfo('workflow_group', $info['workflow']['workflow_group_id']); + } + if ($model->page_content_id) { + $info['page_content'] = $this->getAuditInfo('page_content', $model->page_content_id); + } else if ($info['workflow']) { + // if there's no page_content_id right in the audit trail, try the workflow['page_content_id'] + $info['page_content'] = $this->getAuditInfo('page_content', $info['workflow']['page_content_id']); + } + if ($model->page_id || $model->asset == 'page') { + $info['page'] = $model->asset == 'page'?$this->getAuditInfo('page', $model->asset_id):$this->getAuditInfo('page', $model->page_id); + } else if ($info['page_content']) { + // if there's no page_id right in the audit trail, try the page_content['page_id'] + $info['page'] = $this->getAuditInfo('page', $info['page_content']['page_id']); + } else if ($info['workflow']) { + // if there's no page_id or page_content_id right in the audit trail, try the workflow['page_id'] + $info['page'] = $this->getAuditInfo('page', $info['workflow']['page_id']); + } + $info['action_taken'] = $this->actionToText($model->action_taken); + $info['ip'] = $model->ip; + $info['created'] = $model->cms_created; + return $info; + } + + /** + * checkRSSFeed - Checks the level of the user and exposes a link to an audit trail RSS feed + * to that user if they're an admin level or higher. + * + * @return void + **/ + function checkRSSFeed() { + // Check the user level - this only shows up for admins or higher. + $auth = new NAuth(); + $current_user_level = $auth->getAuthData('user_level'); + $user_id = $auth->currentUserID(); + if ($current_user_level >= N_USER_ADMIN) { + // Get their feed token if they have it. + $cms_user = NModel::factory('cms_auth'); + $feed_token = $cms_user->getFeedToken($user_id); + unset($cms_user); + + // If they don't have one, we should help them to generate it. + if (!isset($feed_token)) { + $rss = '

Click here to generate a private RSS feed

'; + } else { + $rss = '

Private RSS Feed of Audit Trail Activity - Regenerate Token

'; + } + + // Then show the link so that they can put it into their feed reader. + $this->set('rss_feed', $rss); + } + unset($auth); + } + + /** + * getAuditInfo - gets additional audit trail information by looking up foreign keys. + * + * @param string Name of a particular model. + * @param int Id of a record in that particular model. + * @return array Information related to $model_name and $id. + **/ + function getAuditInfo($model_name, $id) { + // checks for deleted and non-deleted records using special field + $info = false; + $fctrl = &NController::singleton($model_name); + if ($fctrl && $fmodel = &$fctrl->getDefaultModel()) { + $fmodel->reset(); + if (!$fmodel->get($id)) { + $fmodel->reset(); + $fields = $fmodel->fields(); + if (in_array('cms_deleted', $fields)) { + $fmodel->cms_deleted = 1; + } + $fmodel->get($id); + } + $info = $fmodel->{$fmodel->primaryKey()}?$fmodel->toArray():false; + if ($info && (!isset($info['_headline']) || !$info['_headline'])) { + $info['_headline'] = $fmodel->makeHeadline(); + } + unset($fmodel); + } + return $info; + } + + /** + * insert - Actually insert a cms_audit_trail record. Must be logged in to nterchange + * for this to succeed. + * NOTE: If you need to log an audit trail record without being logged in (eg. timed content removal) + * there is an alternate method in the cms_audit_trail model. + * + * @param array Required params - asset, asset_id, action_taken + * @return void + **/ + function insert($params=array()) { + $this->_auth = new NAuth; + if (empty($params)) return false; + $required_params = array('asset', 'asset_id', 'action_taken'); + foreach ($required_params as $param) { + if (!isset($params[$param])) return false; + } + $model = &$this->getDefaultModel(); + // apply fields in the model + $fields = $model->fields(); + foreach ($fields as $field) { + $model->$field = isset($params[$field])?$params[$field]:null; + } + $model->user_id = $this->_auth->currentUserID(); + $model->ip = NServer::env('REMOTE_ADDR'); + if (in_array('cms_created', $fields)) { + $model->cms_created = $model->now(); + } + if (in_array('cms_modified', $fields)) { + $model->cms_modified = $model->now(); + } + // set the user id if it's applicable and available + if (in_array('cms_modified_by_user', $fields)) { + $model->cms_modified_by_user = $this->_auth->currentUserID(); + } + $model->insert(); + } + + /** + * actionToText - Convert an action_id to it's human readable explanation. + * + * @param int The id of the action. + * @return string What that id actually means. + **/ + function actionToText($action) { + $txt = ''; + switch ($action) { + case AUDIT_ACTION_INSERT: + $txt = 'Inserted'; + break; + case AUDIT_ACTION_UPDATE: + $txt = 'Updated'; + break; + case AUDIT_ACTION_DELETE: + $txt = 'Deleted'; + break; + case AUDIT_ACTION_CONTENT_ADDEXISTING: + $txt = 'Added Existing Content'; + break; + case AUDIT_ACTION_CONTENT_ADDNEW: + $txt = 'Added New Content to a page'; + break; + case AUDIT_ACTION_CONTENT_REMOVE: + $txt = 'Removed Content from a page'; + break; + case AUDIT_ACTION_WORKFLOW_START: + $txt = 'Submitted Workflow'; + break; + case AUDIT_ACTION_WORKFLOW_SUBMIT: + $txt = 'Submitted Workflow'; + break; + case AUDIT_ACTION_WORKFLOW_APPROVE: + $txt = 'Approved Workflow'; + break; + case AUDIT_ACTION_WORKFLOW_APPROVEPUBLISH: + $txt = 'Approved & Published Workflow'; + break; + case AUDIT_ACTION_WORKFLOW_APPROVEREMOVE: + $txt = 'Approved & Removed Workflow Content'; + break; + case AUDIT_ACTION_WORKFLOW_DECLINE: + $txt = 'Declined Workflow'; + break; + case AUDIT_ACTION_DRAFT_SAVE: + $txt = 'Saved a Draft'; + break; + case AUDIT_ACTION_DRAFT_DELETE: + $txt = 'Deleted a Draft'; + break; + case AUDIT_ACTION_DELETE_ASSET_VERSIONS: + $txt = 'Removed all old content versions'; + break; + case AUDIT_ACTION_LOGIN: + $txt = 'Logged into nterchange'; + break; + } + return $txt; + } + + // override all the crud functions so that no one can edit or manually create an audit trail + // insert is already overridden above with a custom method + function create() { + $this->redirectTo('viewlist'); + } + function edit() { + $this->redirectTo('viewlist'); + } + function update() { + $this->redirectTo('viewlist'); + } + function delete() { + $this->redirectTo('viewlist'); + } + + function &getDefaultModel() { + $model = &$this->loadModel('cms_audit_trail'); + return $model; + } +} +?> diff --git a/app/controllers/cms_asset_info_controller.php b/app/controllers/cms_asset_info_controller.php new file mode 100644 index 0000000..4b119db --- /dev/null +++ b/app/controllers/cms_asset_info_controller.php @@ -0,0 +1,139 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAssetInfoController extends AdminController { + function __construct() { + $this->name = 'cms_asset_info'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + $this->page_title = 'Assets'; + $this->login_required = true; + parent::__construct(); + } + + /** + * AssetList - Return an array of all assets in cms_asset_info table unless + * $connectable is set - then use $model->not_connectable to leave those + * assets out. + * + * @param boolean Whether you want only connectable assets. + * @return array An array with all of the possible assets. + **/ + function AssetList ($connectable=false) { + // Grab the list of available assets. + $assets = &NModel::factory($this->name); + if ($assets->find()) { + while ($assets->fetch()) { + $asset_list[] = $assets->toArray(); + } + unset($assets); + foreach ($asset_list as $asset) { + $model = &NModel::factory($asset['asset']); + // If you don't want any non_connectable assets in the array. + if ($model && $connectable && !isset($model->not_connectable)) { + $final_assets[] = $asset; + // Or if you just want them all. + } elseif($model && !$connectable) { + $final_assets[] = $asset; + } + unset($model); + } + return $final_assets; + } + } + + /** + * doesAssetModelFileExist - Check for the existance of an asset's model file. + * Check in the frontend and backend. + * + * @param string The name of the asset + * @return boolean Does it exist or not? + **/ + function doesAssetModelFileExist($asset){ + $full_path_filename = ASSET_DIR . '/models/' . $asset . '.php'; + if (file_exists($full_path_filename)) { + return true; + } else { + // Check in nterchange backend proper. + $full_path_filename = BASE_DIR . '/app/models/' . $asset . '.php'; + if (file_exists($full_path_filename)) { + return true; + } + return false; + } + } + + /** + * doesAssetControllerFileExist - Check for the existance of an asset's controller file. + * Check in the frontend and backend. + * + * @param string The name of the asset + * @return boolean Does it exist or not? + **/ + function doesAssetControllerFileExist($asset){ + $full_path_filename = ASSET_DIR . '/controllers/' . $asset . '_controller.php'; + if (file_exists($full_path_filename)) { + return true; + } else { + // Check in nterchange backend proper. + $full_path_filename = BASE_DIR . '/app/controllers/' . $asset . '_controller.php'; + if (file_exists($full_path_filename)) { + return true; + } + return false; + } + } + + /** + * doesAssetDatabaseTableExist - Check for the existance of an asset's database table. + * + * @param string The name of the asset + * @return boolean Does it exist or not? + **/ + function doesAssetDatabaseTableExist($asset){ + $model = &NModel::factory($asset); + if ($model) { + if ($model->_fields) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + function &getDefaultModel() { + $model = &$this->loadModel($this->name); + return $model; + } + + /** + * postGenerateForm - Just setting some validation rules for the forms. + * + * @return void + **/ + function postGenerateForm(&$form) { + $form->removeElement('__header__'); + $form->addRule('asset', 'We need to have an asset.', 'required', null, 'client'); + $form->addRule('asset', 'Letters, numbers, dashes and underscores - without a suffix, spaces or punctuation.', 'regex', '/^[a-zA-Z0-9_-]+$/', 'client'); + $form->addRule('asset_name', 'We need to have a name for this asset.', 'required', null, 'client'); + } +} +?> \ No newline at end of file diff --git a/app/controllers/cms_asset_template_controller.php b/app/controllers/cms_asset_template_controller.php new file mode 100644 index 0000000..49a4a60 --- /dev/null +++ b/app/controllers/cms_asset_template_controller.php @@ -0,0 +1,159 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAssetTemplateController extends AdminController { + function __construct() { + $this->name = 'cms_asset_template'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + $this->login_required = true; + $this->page_title = 'Asset / Container Templates'; + parent::__construct(); + } + + function getAssetTemplate($asset, $container_id) { + $model = &NModel::factory($this->name); + $model->asset = (string)$asset; + $model->page_template_container_id = (int)$container_id; + $ret = ''; + if ($model->find(null, true)) { + $ret = $model->template_filename; + } + unset($model); + return $ret; + } + + function viewlist($page_template_container) { + $this->loadSubnav($page_template_container); + $this->auto_render = false; + $html = ''; + if (!$page_template_container) { + // This is a bit of a hack. + header ('Location: /nterchange/page_template/viewlist'); + } + $model = $this->getDefaultModel($this->name); + $model->page_template_container_id = $page_template_container; + $this->set('page_template_container_id', $page_template_container); + // Let's get the Container Name. + if ($page_template_container = $model->getLink('page_template_container_id', 'page_template_containers')) { + $this->set('page_template_container_name', $page_template_container->container_name); + // With the name, let's get the template name and filename too. + $page_template = &NModel::factory('page_template'); + $page_template->id = $page_template_container->page_template_id; + $this->set('page_template_id', $page_template_container->page_template_id); + if ($page_template->find()) { + while ($page_template->fetch()) { + $page_template_tmp = $page_template->toArray(); + $this->set('page_template_filename', $page_template_tmp['template_filename']); + $this->set('page_template_name', $page_template_tmp['template_name']); + } + } + } + if ($model->find()) { + while ($model->fetch()) { + $arr = $model->toArray(); + $arr['_headline'] = isset($arr['cms_headline']) && $arr['cms_headline']?$arr['cms_headline']:$model->makeHeadline(); + $models[] = $arr; + unset($arr); + $html .= $this->set('rows', $models); + } + $html .= $this->set(array('rows'=>$models, 'asset'=>$this->name, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + } else { + $this->set('notice', 'There are no assets associated with that container.'); + } + $html .= $this->set(array('asset'=>$this->name, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + $html .= $this->render(array('layout'=>'default')); + return $html; + } + + function create($parameter=null, $layout=true) { + $this->page_template_container_id = $parameter; + $this->loadSubnav($parameter); + parent::create($parameter); + } + + function edit($parameter) { + $this->page_template_container_id = $this->getPTCIFromId($parameter); + $this->set('page_template_container_id', $this->page_template_container_id); + $this->loadSubnav($parameter); + parent::edit($parameter); + } + + // Get page_template_container_id from cms_asset_template_id + function getPTCIFromId($id) { + $model = &NModel::factory($this->name); + $model->id = $id; + if($model->find()) { + while ($model->fetch()) { + $result = $model->toArray(); + } + $page_template_container_id = $result['page_template_container_id']; + // Set the asset name for postGenerateForm. + $this->passed_asset = $result['asset']; + } + unset($model); + return $page_template_container_id; + } + + function postGenerateForm(&$form) { + $form->removeElement('__header__'); + // Set the container in the menu as passed by $parameter + $container_group = &$form->getElement('page_template_container_id'); + $container_group->setSelected($this->page_template_container_id); + // Not sure I should do this - but it seems to help with confusion. + $container_group->freeze(); + + // Grab the asset list and create an array for QuickForm. + $assets = &NController::factory('cms_asset_info'); + $array_of_assets = $assets->AssetList(true); + foreach ($array_of_assets as $asset) { + $select_array[$asset['asset']] = $asset['asset_name']; + } + // Add the element in place of the current asset form item. + $form->removeElement('asset'); + $new_select = &$form->addElement('select', 'asset', 'Asset:', $select_array); + $form->insertElementBefore($form->removeElement('asset', false), 'template_filename'); + // Set the asset if passed by edit. + if (isset($this->passed_asset)) { + $new_select->setSelected($this->passed_asset); + } + $form->addRule('template_filename', 'We need to have a template filename.', 'required', null, 'client'); + $form->addRule('template_filename', 'Letters, numbers, dashes and underscores - without a suffix, spaces or punctuation.', 'regex', '/^[a-zA-Z0-9_-]+$/', 'client'); + } + + function doesAssetTemplateExist($filename, $asset){ + $full_path_filename = ASSET_DIR . '/views/' . $asset . '/' . $filename . '.' . DEFAULT_PAGE_EXTENSION; + if (file_exists($full_path_filename)) { + return true; + } else { + // Check in nterchange backend proper. + $full_path_filename = BASE_DIR . '/app/views/' . $asset . '/' . $filename . '.' . DEFAULT_PAGE_EXTENSION; + if (file_exists($full_path_filename)) { + return true; + } + return false; + } + } + + function &getDefaultModel() { + $model = &$this->loadModel('cms_asset_template'); + return $model; + } +} +?> \ No newline at end of file diff --git a/app/controllers/cms_drafts_controller.php b/app/controllers/cms_drafts_controller.php new file mode 100644 index 0000000..19c89d6 --- /dev/null +++ b/app/controllers/cms_drafts_controller.php @@ -0,0 +1,60 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsDraftsController extends AppController { + function __construct() { + $this->name = 'cms_drafts'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + $this->login_required = true; + parent::__construct(); + } + + function delete($parameter) { + if (empty($parameter)) { + $this->redirectTo(array('dashboard')); + } + // load the model layer with info + $model = &NModel::factory($this->name); + if (!$model) $this->redirectTo(array('dashboard')); + if ($model->get($parameter)) { + // if the content record is flagged with cms_draft=1, then the content has never been published and should be deleted altogether + $content_model = &NModel::factory($model->asset); + if ($content_model && $content_model->get($model->asset_id) && $content_model->cms_draft == 1) { + $content_model->delete(); + } + unset($content_model); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail before delete so we don't lose the values + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$this->name, 'asset_id'=>$model->{$model->primaryKey()}, 'action_taken'=>AUDIT_ACTION_DRAFT_DELETE)); + unset($audit_trail); + } + $model->delete(); + if (isset($this->params['_referer']) && $this->params['_referer']) { + header('Location:' . urldecode($this->params['_referer'])); + exit; + } + $this->postProcessForm($model->toArray()); + $this->flash->set('notice', 'Draft deleted.'); + } + $this->redirectTo(array('dashboard')); + } +} +?> \ No newline at end of file diff --git a/app/controllers/code_caller_controller.php b/app/controllers/code_caller_controller.php new file mode 100644 index 0000000..2eac01d --- /dev/null +++ b/app/controllers/code_caller_controller.php @@ -0,0 +1,103 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CodeCallerController extends AssetController { + function __construct() { + $this->name = 'code_caller'; + $this->versioning = true; + $this->base_view_dir = BASE_DIR; + parent::__construct(); + } + + function render($options) { + $in_nterchange = defined('IN_NTERCHANGE')?constant('IN_NTERCHANGE'):false; + $in_surftoedit = defined('IN_SURFTOEDIT')?constant('IN_SURFTOEDIT'):false; + if (!$in_nterchange || $in_surftoedit) { + $model = &$this->getDefaultModel(); + $content = $model->content; + require_once 'vendor/JSON.php'; + $json = new Services_JSON(); + if ($code = $json->decode($content) && !empty($code)) { + if (isset($code->controller) && isset($code->action)) { + $this->getContent((array) $code, $model->dynamic); + } + } else { + while (false !== ($pos = strpos($content, '{call'))) { + $pos2 = strpos($content, '}', $pos)+1; + $str = substr($content, $pos, $pos2-$pos); + // clean up the string + $str = trim(str_replace(array('{call ', '}'), '', $str)); + // replace value + $value = ''; + // find matches + preg_match_all('/\s?([^=]+)=[\"\']?([^\"\'\s$]+)[\"\']?/', $str, $matches); + // push the matches into an array if they exists + if (isset($matches[0])) { + $params = array(); + for ($i=0;$igetContent($params, $model->dynamic); + } + $content = substr($content, 0, $pos) . $value . substr($content, $pos2 + 1); + } + } + $model->content = $content; + $this->set($model->toArray()); + unset($json); + } + return parent::render($options); + } + + function getContent($params, $dynamic) { + $content = ''; + $controller = $params['controller']; + $action = $params['action']; + unset($params['controller'], $params['action']); + include_once 'controller/inflector.php'; + $method = Inflector::camelize($action); + if ($ctrl = &NController::factory($controller)) { + if ($dynamic) { + $content = $this->dynamicPHP($ctrl, $method, NController::getIncludePath($controller), $params); + } else { + $content = $ctrl->$method($params); + } + unset($ctrl); + } + return $content; + } + + function dynamicPHP(&$obj, $method, $include_file, $params=array()) { + $ret = ''; + if (is_object($obj) && method_exists($obj, $method)) { + $ret .= 'wrapSanitizedSerializer($obj) . ';'; + $ret .= '$params = ' . $this->wrapSanitizedSerializer($params) . ';'; + $ret .= 'print $obj->' . $method . '($params);'; + $ret .= '?>'; + } + return $ret; + } + + private function wrapSanitizedSerializer($obj) { + $encoded_serialized_string = base64_encode(serialize($obj)); + return "unserialize(base64_decode('$encoded_serialized_string'))"; + } +} +?> diff --git a/app/controllers/content_controller.php b/app/controllers/content_controller.php new file mode 100644 index 0000000..1569c75 --- /dev/null +++ b/app/controllers/content_controller.php @@ -0,0 +1,58 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class ContentController extends nterchangeController { + var $user_level_required = N_USER_NORIGHTS; + var $name = 'content'; + var $page_title = 'Content'; + + function __construct() { + if (is_array($this->login_required)) { + $this->login_required[] = 'list_assets'; + } + parent::__construct(); + } + + function index($parameter) { + $this->redirectTo('list_assets', $parameter); + } + + function listAssets($parameter) { + $this->auto_render = false; + $asset_model = &$this->getDefaultModel(); + if ($asset_model->find()) { + $assets = array(); + while ($asset_model->fetch()) { + $assets[] = $asset_model->toArray(); + } + $this->set(array('assets'=>$assets)); + } + $this->render(array('layout'=>'default')); + } + + function &getDefaultModel() { + if (get_class($this) == __CLASS__) { + $ret = &$this->loadModel('cms_asset_info'); + } else { + $ret = &parent::getDefaultModel(); + } + return $ret; + } +} +?> \ No newline at end of file diff --git a/app/controllers/csv_export_controller.php b/app/controllers/csv_export_controller.php new file mode 100644 index 0000000..73c0218 --- /dev/null +++ b/app/controllers/csv_export_controller.php @@ -0,0 +1,262 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CsvExportController extends AdminController { + var $default_foreign_keys = array('cms_modified_by_user'=>array('cms_auth', 'real_name')); + var $default_field_exclusions = array('id'=>true, 'cms_active'=>true, 'cms_draft'=>true, 'cms_deleted'=>true, 'cms_headline'=>true); + + function __construct() { + $this->name = 'csv_export'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_EDITOR; + $this->login_required = true; + $this->page_title = 'CSV Export'; + parent::__construct(); + } + + function export($model_name) { + if (isset($model_name)) { + $model = NModel::factory($model_name); + // Foreign Key Lookup Support + if (isset($model->excel_export)) { + $model_foreign_keys = $model->excel_export; + // Default standard foreign keys get added and merged here. + $foreign_keys = array_merge($this->default_foreign_keys, $model_foreign_keys); + } else { + $foreign_keys = $this->default_foreign_keys; + } + // Field Inclusion and Exclusion Support + if (isset($model->excel_exclude_fields)) { + $model_excel_inclusions = $model->excel_exclude_fields; + $field_exclusions = array_merge($this->default_field_exclusions, $model_excel_inclusions); + } else { + $field_exclusions = $this->default_field_exclusions; + } + // If $_GET['search'] is set, only export those items. + $search = isset($_GET['search'])?$_GET['search']:null; + $search_field = isset($_GET['search_field'])?$_GET['search_field']:null; + if (isset($search) && $search != null) { + if (!$search_field && $search_field != null) { + $acon = NController::factory('asset'); + $search_field = isset($model->search_field)?$model->search_field:$acon->search_field; + unset($acon); + } + } + $options = $search?array('conditions'=>"$search_field LIKE '%$search%'"):array(); + // Can set options in the model about items exported to the Excel. + // Only export items that meet a certain criteria - not everything in the list. + // For example: $this->viewlist_options = array('conditions'=>"cms_modified_by_user = '4'"); + if (isset($model->viewlist_options)) { + foreach ($model->viewlist_options as $key => $val) { + if (isset($options[$key])) { + $options[$key] .= ' AND ' . $val; + } else { + $options[$key] = "$val"; + } + } + } + + if ($model->find($options)) { + $fields = $model->fields(); + // Add additional custom fields here from the model file. + if (isset($model->excel_extra_fields)) { + foreach ($model->excel_extra_fields as $key => $value) { + $fields[] = $key; + } + } + // Creating a workbook + $filename = $_SERVER['DOCUMENT_ROOT'] . UPLOAD_DIR . '/' . rand(1,1000) . '-file.csv'; + $fp = fopen($filename, 'w'); + + // Creating a workbook and sending it directly out to a browser. + //$fp = fopen('php://output', 'w'); + + // Let's add the field names to the title line. + // Leave out a few. + $x = 0; + foreach ($fields as $field) { + $exclude_this = array_key_exists($field, $field_exclusions); + if ($exclude_this && $field_exclusions[$field] == true) { + // do nothing + } else { + $good_fields[] = $field; + } + } + //$field_string = implode(',', $good_fields); + fputcsv($fp, $good_fields); + + // Now here comes the data. + $y = 1; + while ($model->fetch()) { + $data_fields = array(); + $item = $model->toArray(); + // For reference while we're working with things. + $original_item = array(); + $original_item = $item; + $x = 0; + foreach ($fields as $field) { + $exclude_this = array_key_exists($field, $field_exclusions); + if ($exclude_this && $field_exclusions[$field] == true) { + // do nothing + } else { + // Look for foreign keys and replace if assigned. + foreach($foreign_keys as $foreign_key => $foreign_key_value) { + if ($field == $foreign_key) { + $fk_model_name = $foreign_key_value[0]; + $fk_model_headline = $foreign_key_value[1]; + $fk_model = NModel::factory($fk_model_name); + if ($fk_model && ($fk_model->get($item[$field]))) { + $item[$field] = $fk_model->{$fk_model_headline}; + } + unset($fk_model); + } + } + + //Look for bitmask fields and replace with string value instead of numeric total + if (is_array($model->bitmask_fields) && count($model->bitmask_fields)) { + $bitmask_keys = array_keys($model->bitmask_fields); + if (in_array($field, $bitmask_keys)) { + $bitmask_total = $item[$field]; + $value_str = ''; + $i = 0; + foreach($model->bitmask_fields[$field] as $bit=>$val) { + if($bit & $bitmask_total) { + if($i > 0) { + $value_str .= ', '; + } + $value_str .= $val; + $i ++; + } + } + $item[$field] = $value_str; + } + } + + // Any extra fields get dealt with here. + if (isset($model->excel_extra_fields)) { + foreach ($model->excel_extra_fields as $key => $value) { + if ($field == $key) { + $extra_name = $value[0]; + $extra_attribute = $value[1]; + $extra_key = $value[2]; + $extra_info = NModel::factory($extra_name); + if (method_exists($extra_info, $extra_attribute)) { + $item[$field] = $extra_info->$extra_attribute($original_item["$extra_key"]); + } else { + $extra_info->get($original_item["$extra_key"]); + $item[$field] = $extra_info->$extra_attribute; + } + unset($extra_info); + } + } + } + // If it's an uploaded file, put the address in the conf.php before it so that it + // turns into a link in Excel. + if (eregi(UPLOAD_DIR, $item[$field])) { + $item[$field] = PUBLIC_SITE . ereg_replace("^/", "", $item[$field]); + } + $fixed_item = $this->convert_characters($item[$field]); + $data_fields[] = $fixed_item; + } + } + //$data_string = implode(',', $data_fields); + fputcsv($fp, $data_fields); + unset($original_item); + unset($item); + unset($data_fields); + } + // Close the file. + fclose($fp); + $download = new NDownload; + $download->serveFile($filename); + unlink($filename); + } + } + } + + function convert_characters($text) { + $dict = array(chr(138) => 'ä', chr(141) => 'ç', chr(142) => 'é', chr(145) => "'", chr(146) => "'", + chr(147) => '"', chr(148) => '"', chr(150) => '-', chr(151) => '-', chr(154) => 'ö', + chr(157) => 'ù', chr(158) => 'û', chr(160) => ' ', chr(161) => '°', chr(173) => '≠', + chr(188) => 'º', chr(189) => 'Ω', chr(190) => 'æ', chr(191) => 'ø', chr(192) => '¿', + chr(193) => '¡', chr(194) => '¬', chr(195) => '√', chr(196) => 'ƒ', chr(197) => '≈', + chr(198) => '∆', chr(199) => '«', chr(200) => '»', chr(201) => '…', chr(202) => ' ', + chr(203) => 'À', chr(204) => 'Ã', chr(205) => 'Õ', chr(206) => 'Œ', chr(207) => 'œ', + chr(209) => '—', chr(210) => '“', chr(211) => '”', chr(212) => '‘', chr(213) => '’', + chr(214) => '÷', chr(216) => 'ÿ', chr(217) => 'Ÿ', chr(218) => '⁄', chr(219) => '€', + chr(220) => '‹', chr(221) => '›', chr(223) => 'fl', chr(224) => '‡', chr(225) => '·', + chr(226) => '‚', chr(227) => '„', chr(228) => '‰', chr(229) => 'Â', chr(230) => 'Ê', + chr(231) => 'Á', chr(232) => 'Ë', chr(233) => 'È', chr(234) => 'Í', chr(235) => 'Î', + chr(236) => 'Ï', chr(237) => 'Ì', chr(238) => 'Ó', chr(239) => 'Ô', chr(241) => 'Ò', + chr(242) => 'Ú', chr(243) => 'Û', chr(244) => 'Ù', chr(245) => 'ı', chr(246) => 'ˆ', + chr(248) => '¯', chr(249) => '˘', chr(250) => '˙', chr(251) => '˚', chr(252) => '¸', + chr(253) => '˝', chr(255) => 'ˇ', + + 'Š' => 'ä', '' => 'ç', 'Ž' => 'é', '‘' => "'", '’' => "'", + '“' => '"', '”' => '"', '–' => '-', '—' => '-', 'š' => 'ö', + '' => 'ù', 'ž' => 'û', ' ' => ' ', '¡' => '°', '­' => '≠', + '¼' => 'º', '½' => 'Ω', '¾' => 'æ', '¿' => 'ø', 'À' => '¿', + 'Á' => '¡', 'Â' => '¬', 'Ã' => '√', 'Ä' => 'ƒ', 'Å' => '≈', + 'Æ' => '∆', 'Ç' => '«', 'È' => '»', 'É' => '…', 'Ê' => ' ', + 'Ë' => 'À', 'Ì' => 'Ã', 'Í' => 'Õ', 'Î' => 'Œ', 'Ï' => 'œ', + 'Ñ' => '—', 'Ò' => '“', 'Ó' => '”', 'Ô' => '‘', 'Õ' => '’', + 'Ö' => '÷', 'Ø' => 'ÿ', 'Ù' => 'Ÿ', 'Ú' => '⁄', 'Û' => '€', + 'Ü' => '‹', 'Ý' => '›', 'ß' => 'fl', 'à' => '‡', 'á' => '·', + 'â' => '‚', 'ã' => '„', 'ä' => '‰', 'å' => 'Â', 'æ' => 'Ê', + 'ç' => 'Á', 'è' => 'Ë', 'é' => 'È', 'ê' => 'Í', 'ë' => 'Î', + 'ì' => 'Ï', 'í' => 'Ì', 'î' => 'Ó', 'ï' => 'Ô', 'ñ' => 'Ò', + 'ò' => 'Ú', 'ó' => 'Û', 'ô' => 'Ù', 'õ' => 'ı', 'ö' => 'ˆ', + 'ø' => '¯', 'ù' => '˘', 'ú' => '˙', 'û' => '˚', 'ü' => '¸', + 'ý' => '˝', 'ÿ' => 'ˇ', + + '≈ ' => 'ä', '≈í' => 'å', '≈Ω' => 'é', '≈°' => 'ö', '≈ì' => 'ú', '≈æ' => 'û', '≈∏' => 'ü', + '¬•' => '•', '¬µ' => 'µ', '√Ä' => '¿', '√Å' => '¡', '√Ç' => '¬', '√É' => '√', '√Ñ' => 'ƒ', + '√Ö' => '≈', '√Ü' => '∆', '√á' => '«', '√à' => '»', '√â' => '…', '√ä' => ' ', '√ã' => 'À', + '√å' => 'Ã', '√ç' => 'Õ', '√é' => 'Œ', '√è' => 'œ', '√ê' => '–', '√ë' => '—', '√í' => '“', + '√ì' => '”', '√î' => '‘', '√ï' => '’', '√ñ' => '÷', '√ò' => 'ÿ', '√ô' => 'Ÿ', '√ö' => '⁄', + '√õ' => '€', '√ú' => '‹', '√ù' => '›', '√ü' => 'fl', '√ ' => '‡', '√°' => '·', '√¢' => '‚', + '√£' => '„', '√§' => '‰', '√•' => 'Â', '√¶' => 'Ê', '√ß' => 'Á', '√®' => 'Ë', '√©' => 'È', + '√™' => 'Í', '√´' => 'Î', '√¨' => 'Ï', '√≠' => 'Ì', '√Æ' => 'Ó', '√Ø' => 'Ô', '√∞' => '', + '√±' => 'Ò', '√≤' => 'Ú', '√≥' => 'Û', '√¥' => 'Ù', '√µ' => 'ı', '√∂' => 'ˆ', '√∏' => '¯', + '√π' => '˘', '√∫' => '˙', '√ª' => '˚', '√º' => '¸', '√Ω' => '˝', '√ø' => 'ˇ', "¬ø" => 'ø', + '¬º' => 'º', '¬Ω' => 'Ω', '¬æ' => 'æ', '≈ ' => 'ä', '¬ç' => 'ç', '¬ù' => 'ù', '¬°' => '°', '¬≠' => '-', + '‚Äú' => '"', '‚Äù' => '"', '‚Äì' => '-', "\n" => ' ', "\r" => ' ', '‚Äô' => "'", '‚Ä' => '"', + + '¿' => 'ø', 'Æ' => '∆', 'Á' => '¡', 'Â' => '¬', 'À' => '¿', + 'Å' => '≈', 'Ã' => '√', 'Ä' => 'ƒ', 'Ç' => '«', 'Ð' => '–', + 'É' => '…', 'Ê' => ' ', 'È' => '»', 'Ë' => 'À', 'Í' => 'Õ', + 'Î' => 'Œ', 'Ì' => 'Ã', 'Ï' => 'œ', 'Ñ' => '—', 'Ó' => '”', + 'Ô' => '‘', 'Ò' => '“', 'Ø' => 'ÿ', 'Õ' => '’', 'Ö' => '÷', + 'Ú' => '⁄', 'Û' => '€', 'Ù' => 'Ÿ', 'Ü' => '‹', 'Ý' => '›', + 'á' => '·', 'â' => '‚', 'æ' => 'Ê', 'à' => '‡', 'å' => 'Â', + 'ã' => '„', 'ä' => '‰', 'ç' => 'Á', 'é' => 'È', 'ê' => 'Í', + 'è' => 'Ë', 'ð' => '', 'ë' => 'Î', '½' => 'Ω', '¼' => 'º', + '¾' => 'æ', 'í' => 'Ì', 'î' => 'Ó', '¡' => '°', 'ì' => 'Ï', + '¿' => 'ø', 'ï' => 'Ô', '—' => 'ó', 'µ' => 'µ', '–' => 'ñ', + 'ñ' => 'Ò', 'ó' => 'Û', 'ô' => 'Ù', 'ò' => 'Ú', 'ø' => '¯', + 'õ' => 'ı', 'ö' => 'ˆ', '"' => '"', '­' => '≠', 'ß' => 'fl', + 'ú' => '˙', 'û' => '˚', 'ù' => '˘', 'ü' => '¸', 'ý' => '˝', + '¥' => '•', 'ÿ' => 'ˇ', '—' => '-', "\n" => ' ', "\r" => ' '); + + return strtr($text, $dict); + } + +} +?> \ No newline at end of file diff --git a/app/controllers/dashboard_controller.php b/app/controllers/dashboard_controller.php new file mode 100644 index 0000000..09a94f7 --- /dev/null +++ b/app/controllers/dashboard_controller.php @@ -0,0 +1,314 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class DashboardController extends nterchangeController { + function __construct() { + $this->name = 'dashboard'; + $this->default_action = 'show'; + $this->page_title = 'Your Dashboard'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + $this->login_required = true; + $this->base_view_dir = ROOT_DIR; + parent::__construct(); + } + + function index($parameter) { + $this->auto_render = false; + $sidebar_content = $this->render(array('action'=>'description', 'return'=>true)); + if (SITE_DRAFTS) { + $draft_model = &NModel::factory('cms_drafts'); + if ($draft_model) { + $draft_model->cms_modified_by_user = $this->_auth->currentUserId(); + if ($draft_model->find()) { + while ($draft_model->fetch()) { + $asset_ctrl = &NController::factory($draft_model->asset); + $asset_model = &$draft_model->getLink('asset_id', $draft_model->asset); + if ($asset_model) { + $this->set(array('draft'=>$draft_model->toArray(), 'asset_name'=>$asset_ctrl->page_title?$asset_ctrl->page_title:Inflector::humanize($asset_ctrl->name), 'asset'=>$draft_model->asset)); + $this->set($asset_model->toArray()); + $this->setAppend('drafts', $this->render(array('action'=>'draft_record', 'return'=>true))); + } + unset($asset_ctrl); + unset($asset_model); + } + } else { + $this->set('drafts', $this->render(array('action'=>'no_drafts', 'return'=>true))); + } + } + } + // load all workflow output into this variable to be assigned later + $workflow_html = ''; + if (SITE_WORKFLOW) { + $sidebar_content .= $this->render(array('action'=>'workflow_description', 'return'=>true)); + $user_id = $this->_auth->currentUserId(); + // If user is an admin, and has any unsubmitted workflow in groups they don't belong to, display them first + if ($this->_auth->getAuthData('user_level') >= N_USER_ADMIN) { + $workflow = &NController::factory('workflow'); + $workflow_model = &NModel::factory('workflow'); + $workflow_model_pk = $workflow_model->primaryKey(); + $workflow_model->cms_modified_by_user = $user_id; + $workflow_model->submitted = 0; + if ($workflow_model->find(array('order_by'=>'page_id'))) { + $admin_workflow_html = ''; + $this->set('workflow_section', 'Unsubmitted Admin Workflows'); + $workflow_html .= $this->render(array('action'=>'workflow_section', 'return'=>true)); + $page_id = 0; + $page_count = 0; + $page_workflows = array(); + while ($workflow_model->fetch()) { + $workflow_users_model = &NModel::factory('workflow_users'); + $workflow_users_model->workflow_group_id = $workflow_model->workflow_group_id; + $workflow_users_model->user_id = $workflow_model->cms_modified_by_user; + if ($workflow_users_model->find()) { + unset($workflow_users_model); + continue; + } + unset($workflow_users_model); + $unsubmitted[] = $workflow_model->$workflow_model_pk; + $page_content_model = &NModel::factory('page_content'); + $page_content_model->get($workflow_model->page_content_id); + $page_model = &$page_content_model->getLink('page_id', 'page'); + $asset_controller = &NController::factory($workflow_model->asset); + $asset_model = &$asset_controller->getDefaultModel(); + $asset_model->get($workflow_model->asset_id); + $this->convertDateTimesToClient($asset_model); + $action = $workflow->actionToString($workflow_model->action); + $cascade_delete = $page_content_model->cms_workflow?true:false; + // set the page title for the following pages + $this->set('page_title', ''); + if ($workflow_model->page_id == $page_id) { + $page_count++; + } else { + $this->set('page_title', $page_model->title); + $admin_workflow_html .= $this->workflowPageSubmit($page_workflows); + $page_id = $workflow_model->page_id; + $page_count = 0; + $page_workflows = array(); + } + $page_workflows[] = $workflow_model->$workflow_model_pk; + $user = &$workflow_model->getLink('cms_modified_by_user', 'cms_auth'); + $this->set(array('process'=>'submit', 'cascade_delete'=>$cascade_delete, 'approved'=>$workflow_model->approved, 'action'=>$action, 'workflow'=>$workflow_model->toArray(), 'page'=>$page_model->toArray(), 'asset'=>$asset_controller, 'row'=>$asset_model->toArray(), 'user'=>($user?$user->toArray():false))); + $admin_workflow_html .= $this->render(array('action'=>'workflow_record', 'return'=>true)); + } + $admin_workflow_html .= $this->workflowPageSubmit($page_workflows); + if ($admin_workflow_html) { + $this->set(array('workflow_title'=>'Admin Workflows')); + $workflow_html .= $this->render(array('action'=>'workflow', 'return'=>true)) . $admin_workflow_html; + unset($admin_workflow_html); + } + } + unset($workflow_model); + unset($workflow); + } + $workflow_users = &$this->loadModel('workflow_users'); + $workflow_users->user_id = $user_id; + if ($workflow_users->find()) { + while ($workflow_users->fetch()) { + // instantiate workflow group object + $workflow_group = &$workflow_users->getLink('workflow_group_id', 'workflow_group'); + // render current workflow group + $this->set($workflow_group->toArray()); + $workflow_html .= $this->render(array('action'=>'workflow', 'return'=>true)); + // instantiate workflow objects + $workflow = &NController::factory('workflow'); + $workflow_model = &$workflow->getDefaultModel(); + $workflow_model_pk = $workflow_model->primaryKey(); + // find unsubmitted workflows that belong to this user + $workflow_model->submitted = 0; + $workflow_model->completed = 0; + $workflow_model->workflow_group_id = $workflow_group->{$workflow_group->primaryKey()}; + $workflow_model->cms_modified_by_user = $user_id; + $unsubmitted = array(); + if ($workflow_model->find(array('order_by'=>'page_id, asset, asset_id, id'))) { + $this->set('workflow_section', 'Unsubmitted Workflows'); + $workflow_html .= $this->render(array('action'=>'workflow_section', 'return'=>true)); + $page_id = 0; + $page_count = 0; + $page_workflows = array(); + while ($workflow_model->fetch()) { + $unsubmitted[] = $workflow_model->$workflow_model_pk; + $page_content_model = &$workflow_model->getLink('page_content_id', 'page_content'); + if (!$page_content_model) continue; + $page_model = &$page_content_model->getLink('page_id', 'page'); + $asset_controller = &NController::factory($workflow_model->asset); + $asset_model = &$asset_controller->getDefaultModel(); + $asset_model->get($workflow_model->asset_id); + $this->convertDateTimesToClient($asset_model); + $action = $workflow->actionToString($workflow_model->action); + // set the page title for the following pages + $this->set('page_title', ''); + if ($workflow_model->page_id == $page_id) { + $page_count++; + } else { + $this->set('page_title', $page_model->title); + $workflow_html .= $this->workflowPageSubmit($page_workflows); + $page_id = $workflow_model->page_id; + $page_count = 0; + $page_workflows = array(); + } + $page_workflows[] = $workflow_model->$workflow_model_pk; + $user = &$workflow_model->getLink('cms_modified_by_user', 'cms_auth'); + $this->convertDateTimesToClient($workflow_model); + $this->set(array('process'=>'submit', 'list_only'=>false, 'approved'=>$workflow_model->approved, 'action'=>$action, 'workflow'=>$workflow_model->toArray(), 'page'=>$page_model->toArray(), 'asset'=>$asset_controller, 'row'=>$asset_model->toArray(), 'user'=>($user?$user->toArray():false))); + $workflow_html .= $this->render(array('action'=>'workflow_record', 'return'=>true)); + } + $workflow_html .= $this->workflowPageSubmit($page_workflows); + } + // find in process workflows, resetting the model object first + $workflow_model->reset(); + $workflow_model->workflow_group_id = $workflow_group->{$workflow_group->primaryKey()}; + $workflow_model->completed = 0; + $conditions = ''; + foreach ($unsubmitted as $id) { + $conditions .= ($conditions?' AND ':'') . "$workflow_model_pk!=$id"; + } + $this->set('workflow_section', 'Workflows in Process'); + $workflow_html .= $this->render(array('action'=>'workflow_section', 'return'=>true)); + $workflow_html_content = ''; + if ($workflow_model->find(array('conditions'=>$conditions, 'order_by'=>'page_id, asset, asset_id, id'))) { + $workflow_models = array(); + while ($workflow_model->fetch()) { + $workflow_models[] = clone($workflow_model); + } + $i = 0; + $current_asset = ''; + foreach ($workflow_models as $w_model) { + if ($w_model->submitted == 0) { + continue; + } + if ($current_asset != $w_model->asset . $w_model->asset_id) { + $current_asset = $w_model->asset . $w_model->asset_id; + if (!$page_content_model = &$w_model->getLink('page_content_id', 'page_content')) continue; + if (!$page_model = &$page_content_model->getLink('page_id', 'page')) continue; + $user_def = $workflow->getWorkflowUser($w_model->workflow_group_id); + if ($user_def) { + $user_role = $user_def->role; + $user_id = $user_def->user_id; + } + $user_rights = $workflow->getWorkflowUserRights($page_model); + $i = 0; + } + $asset_controller = &NController::factory($w_model->asset); + $asset_model = &$asset_controller->getDefaultModel(); + $asset_model->get($w_model->asset_id); + $this->convertDateTimesToClient($asset_model); + $action = $workflow->actionToString($w_model->action); + $all_workflow_users = $workflow->getWorkflowUsers($workflow_model->workflow_group_id); + if (count($all_workflow_users) < 2) { + $i++; + } + if ($i == 0) { + if ($user_rights == WORKFLOW_RIGHT_EDIT) { + $process = 'In Process - ' . ($w_model->approved?'Approved':'Unapproved'); + } else if ($user_rights & WORKFLOW_RIGHT_EDIT) { + // this is someone with editing rights and more. Could be the same user that submitted it. + $process = ($w_model->approved?'In Process - Approved':'editapprove'); + } else { + // This is someone up the line. Let them know something's coming, but they don't need to know what yet. + if ($w_model->approved) { + $process = 'Approved'; + } else { + $process = 'A workflow has been started. You will be notified if/when you need to take action.'; + } + } + } else if ($i == 1) { + if ($user_rights == WORKFLOW_RIGHT_EDIT) { + $process = 'In Process - ' . ($w_model->approved?'Approved':'Unapproved'); + } else if ($user_rights & WORKFLOW_RIGHT_APPROVE && $user_rights & WORKFLOW_RIGHT_PUBLISH) { + // this is someone with Approval rights. Could be the same user that submitted it + $process = 'approve'; + } else { + $process = 'In Process - ' . ($w_model->approved?'Approved':'Unapproved'); + } + } + $user = &$w_model->getLink('cms_modified_by_user', 'cms_auth'); + $this->convertDateTimesToClient($w_model); + $this->set(array('process'=>$process, 'list_only'=>false, 'approved'=>$w_model->approved, 'action'=>$action, 'workflow'=>$w_model->toArray(), 'page'=>$page_model->toArray(), 'asset'=>$asset_controller, 'row'=>$asset_model->toArray(), 'user'=>($user?$user->toArray():false))); + $workflow_html_content .= $this->render(array('action'=>'workflow_record', 'return'=>true)); + $i++; + } + } + $workflow_html .= $workflow_html_content?$workflow_html_content:$this->render(array('action'=>'workflow_norecords', 'return'=>true)); + // find completed workflows, resetting the model object first + $workflow_model->reset(); + $workflow_model->workflow_group_id = $workflow_group->{$workflow_group->primaryKey()}; + $workflow_model->completed = 1; + $workflow_model->parent_workflow = 0; + // bad timg - shouldn't do this here + $workflow_html .= '
' . "\n"; + $this->set('workflow_section', 'Completed Workflows'); + $workflow_html .= $this->render(array('action'=>'workflow_section', 'return'=>true)); + if ($workflow_model->find(array('conditions'=>$conditions, 'order_by'=>'cms_created DESC', 'limit'=>5))) { + $workflow_models = array(); + while ($workflow_model->fetch()) { + $page_model = &NModel::factory('page'); + $page_model->{$page_model->primaryKey()} = $workflow_model->page_id; + // if the page is not deleted, this works + if (!$page_model->find(null, true)) { + // otherwise, specify a deleted page and try again + $page_model->reset(); + $page_model->{$page_model->primaryKey()} = $workflow_model->page_id; + $page_model->cms_deleted = 1; + $page_model->find(null, true); + } + $page_values = $page_model?$page_model->toArray():false; + $asset_controller = &NController::factory($workflow_model->asset); + $asset_model = &$asset_controller->getDefaultModel(); + if (!$asset_model->get($workflow_model->asset_id)) { + $asset_model->reset(); + $asset_model->cms_deleted = 1; + $asset_model->get($workflow_model->asset_id); + } + $this->convertDateTimesToClient($asset_model); + $action = $workflow->actionToString($workflow_model->action); + $user = &$workflow_model->getLink('cms_modified_by_user', 'cms_auth'); + $this->convertDateTimesToClient($workflow_model); + $values = array('process'=>null, 'list_only'=>true, 'approved'=>$workflow_model->approved, 'action'=>$action, 'workflow'=>$workflow_model->toArray(), 'asset'=>$asset_controller, 'row'=>$asset_model->toArray(), 'page'=>$page_values, 'user'=>($user?$user->toArray():false)); + $this->set($values); + $workflow_html .= $this->render(array('action'=>'workflow_record', 'return'=>true)); + } + } + $workflow_html .= '
' . "\n"; + } + } else { + $workflow_html .= $this->render(array('action'=>'no_workflows', 'return'=>true)); + } + $this->set('workflow', $workflow_html); + } + $this->set('SIDEBAR_CONTENT', $sidebar_content); + $this->setAppend('SIDEBAR_CONTENT', $this->render(array('action'=>'nterchange_training', 'return'=>true))); + $this->setAppend('SIDEBAR_CONTENT', $this->render(array('action'=>'dashboard_client_sidebar_content', 'return'=>true))); + $this->render(array('layout'=>'default')); + } + + function workflowPageSubmit(&$page_workflows) { + $this->set('page_workflows', false); + if (count($page_workflows)) { + $this->set('page_workflows', $page_workflows); + return $this->render(array('action'=>'workflow_page_submit', 'return'=>true)); + } + return ''; + } + + function dashboardClientContent() { + $this->render(array('action'=>'dashboard_client_content', 'return'=>false)); + } +} +?> diff --git a/app/controllers/excel_export_controller.php b/app/controllers/excel_export_controller.php new file mode 100644 index 0000000..365e203 --- /dev/null +++ b/app/controllers/excel_export_controller.php @@ -0,0 +1,267 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class ExcelExportController extends AdminController { + var $default_foreign_keys = array('cms_modified_by_user'=>array('cms_auth', 'real_name')); + var $default_field_exclusions = array('id'=>true, 'cms_active'=>true, 'cms_draft'=>true, 'cms_deleted'=>true, 'cms_headline'=>true); + + function __construct() { + $this->name = 'excel_export'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_EDITOR; + $this->login_required = true; + $this->page_title = 'Excel Export'; + parent::__construct(); + } + + function export($model_name) { + if (isset($model_name)) { + $model = NModel::factory($model_name); + // Foreign Key Lookup Support + if (isset($model->excel_export)) { + $model_foreign_keys = $model->excel_export; + // Default standard foreign keys get added and merged here. + $foreign_keys = array_merge($this->default_foreign_keys, $model_foreign_keys); + } else { + $foreign_keys = $this->default_foreign_keys; + } + // Field Inclusion and Exclusion Support + if (isset($model->excel_exclude_fields)) { + $model_excel_inclusions = $model->excel_exclude_fields; + $field_exclusions = array_merge($this->default_field_exclusions, $model_excel_inclusions); + } else { + $field_exclusions = $this->default_field_exclusions; + } + // If $_GET['search'] is set, only export those items. + $search = isset($_GET['search'])?$_GET['search']:null; + $search_field = isset($_GET['search_field'])?$_GET['search_field']:null; + if (isset($search) && $search != null) { + if (!$search_field && $search_field != null) { + $acon = NController::factory('asset'); + $search_field = isset($model->search_field)?$model->search_field:$acon->search_field; + unset($acon); + } + } + $options = $search?array('conditions'=>"$search_field LIKE '%$search%'"):array(); + // Can set options in the model about items exported to the Excel. + // Only export items that meet a certain criteria - not everything in the list. + // For example: $this->viewlist_options = array('conditions'=>"cms_modified_by_user = '4'"); + if (isset($model->viewlist_options)) { + foreach ($model->viewlist_options as $key => $val) { + if (isset($options[$key])) { + $options[$key] .= ' AND ' . $val; + } else { + $options[$key] = "$val"; + } + } + } + + if ($model->find($options)) { + $fields = $model->fields(); + // Add additional custom fields here from the model file. + if (isset($model->excel_extra_fields)) { + foreach ($model->excel_extra_fields as $key => $value) { + $fields[] = $key; + } + } + require_once 'Spreadsheet/Excel/Writer.php'; + + // Creating a workbook + $workbook = new Spreadsheet_Excel_Writer(); + $worksheet =& $workbook->addWorksheet(ucwords(str_replace('_', ' ', $model_name))); + $worksheet->setColumn(2, 4, 20); + $worksheet->setColumn(7, 7, 15); + $worksheet->setColumn(10, 28, 20); + + // Make the title line look a little different + $title =& $workbook->addFormat(); + $title->setBold(); + $title->setAlign('center'); + $title->setBottom(2); + + // Let's add the field names to the title line. + // Leave out a few. + $x = 0; + $worksheet->setRow(0, 18.75); + foreach ($fields as $field) { + $exclude_this = array_key_exists($field, $field_exclusions); + if ($exclude_this && $field_exclusions[$field] == true) { + // do nothing + } else { + $worksheet->write(0, $x, ucwords(str_replace('_', ' ', $field)), $title); + $x++; + } + } + + // Now here comes the data. + $y = 1; + while ($model->fetch()) { + $item = $model->toArray(); + // For reference while we're working with things. + $original_item = array(); + $original_item = $item; + $x = 0; + $worksheet->setRow($y, 18.75); + foreach ($fields as $field) { + $exclude_this = array_key_exists($field, $field_exclusions); + if ($exclude_this && $field_exclusions[$field] == true) { + // do nothing + } else { + // Look for foreign keys and replace if assigned. + foreach($foreign_keys as $foreign_key => $foreign_key_value) { + if ($field == $foreign_key) { + $fk_model_name = $foreign_key_value[0]; + $fk_model_headline = $foreign_key_value[1]; + $fk_model = NModel::factory($fk_model_name); + if ($fk_model && ($fk_model->get($item[$field]))) { + $item[$field] = $fk_model->{$fk_model_headline}; + } + unset($fk_model); + } + } + + //Look for bitmask fields and replace with string value instead of numeric total + if (is_array($model->bitmask_fields) && count($model->bitmask_fields)) { + $bitmask_keys = array_keys($model->bitmask_fields); + if (in_array($field, $bitmask_keys)) { + $bitmask_total = $item[$field]; + $value_str = ''; + $i = 0; + foreach($model->bitmask_fields[$field] as $bit=>$val) { + if($bit & $bitmask_total) { + if($i > 0) { + $value_str .= ', '; + } + $value_str .= $val; + $i ++; + } + } + $item[$field] = $value_str; + } + } + + // Any extra fields get dealt with here. + if (isset($model->excel_extra_fields)) { + foreach ($model->excel_extra_fields as $key => $value) { + if ($field == $key) { + $extra_name = $value[0]; + $extra_attribute = $value[1]; + $extra_key = $value[2]; + $extra_info = NModel::factory($extra_name); + if (method_exists($extra_info, $extra_attribute)) { + $item[$field] = $extra_info->$extra_attribute($original_item["$extra_key"]); + } else { + $extra_info->get($original_item["$extra_key"]); + $item[$field] = $extra_info->$extra_attribute; + } + unset($extra_info); + } + } + } + // If it's an uploaded file, put the address in the conf.php before it so that it + // turns into a link in Excel. + if (eregi(UPLOAD_DIR, $item[$field])) { + $item[$field] = PUBLIC_SITE . ereg_replace("^/", "", $item[$field]); + } + $worksheet->write($y, $x, $this->convert_characters($item[$field])); + $x++; + } + } + $y++; + unset($original_item); + unset($item); + } + // sending HTTP headers + $xls_filename = $model_name . '_entries.xls'; + $workbook->send($xls_filename); + $workbook->close(); + } + } + } + + function convert_characters($text) { + $dict = array(chr(138) => '', chr(141) => '', chr(142) => '', chr(145) => "'", chr(146) => "'", + chr(147) => '"', chr(148) => '"', chr(150) => '-', chr(151) => '-', chr(154) => '', + chr(157) => '', chr(158) => '', chr(160) => ' ', chr(161) => '', chr(173) => '', + chr(188) => '', chr(189) => '', chr(190) => '', chr(191) => '', chr(192) => '', + chr(193) => '', chr(194) => '', chr(195) => '', chr(196) => '', chr(197) => '', + chr(198) => '', chr(199) => '', chr(200) => '', chr(201) => '', chr(202) => '', + chr(203) => '', chr(204) => '', chr(205) => '', chr(206) => '', chr(207) => '', + chr(209) => '', chr(210) => '', chr(211) => '', chr(212) => '', chr(213) => '', + chr(214) => '', chr(216) => '', chr(217) => '', chr(218) => '', chr(219) => '', + chr(220) => '', chr(221) => '', chr(223) => '', chr(224) => '', chr(225) => '', + chr(226) => '', chr(227) => '', chr(228) => '', chr(229) => '', chr(230) => '', + chr(231) => '', chr(232) => '', chr(233) => '', chr(234) => '', chr(235) => '', + chr(236) => '', chr(237) => '', chr(238) => '', chr(239) => '', chr(241) => '', + chr(242) => '', chr(243) => '', chr(244) => '', chr(245) => '', chr(246) => '', + chr(248) => '', chr(249) => '', chr(250) => '', chr(251) => '', chr(252) => '', + chr(253) => '', chr(255) => '', + + 'Š' => '', '' => '', 'Ž' => '', '‘' => "'", '’' => "'", + '“' => '"', '”' => '"', '–' => '-', '—' => '-', 'š' => '', + '' => '', 'ž' => '', ' ' => ' ', '¡' => '', '­' => '', + '¼' => '', '½' => '', '¾' => '', '¿' => '', 'À' => '', + 'Á' => '', 'Â' => '', 'Ã' => '', 'Ä' => '', 'Å' => '', + 'Æ' => '', 'Ç' => '', 'È' => '', 'É' => '', 'Ê' => '', + 'Ë' => '', 'Ì' => '', 'Í' => '', 'Î' => '', 'Ï' => '', + 'Ñ' => '', 'Ò' => '', 'Ó' => '', 'Ô' => '', 'Õ' => '', + 'Ö' => '', 'Ø' => '', 'Ù' => '', 'Ú' => '', 'Û' => '', + 'Ü' => '', 'Ý' => '', 'ß' => '', 'à' => '', 'á' => '', + 'â' => '', 'ã' => '', 'ä' => '', 'å' => '', 'æ' => '', + 'ç' => '', 'è' => '', 'é' => '', 'ê' => '', 'ë' => '', + 'ì' => '', 'í' => '', 'î' => '', 'ï' => '', 'ñ' => '', + 'ò' => '', 'ó' => '', 'ô' => '', 'õ' => '', 'ö' => '', + 'ø' => '', 'ù' => '', 'ú' => '', 'û' => '', 'ü' => '', + 'ý' => '', 'ÿ' => '', + + ' ' => '', 'Œ' => '', 'Ž' => '', 'š' => '', 'œ' => '', 'ž' => '', 'Ÿ' => '', + '¥' => '', 'µ' => '', 'À' => '', 'Á' => '', 'Â' => '', 'Ã' => '', 'Ä' => '', + 'Å' => '', 'Æ' => '', 'Ç' => '', 'È' => '', 'É' => '', 'Ê' => '', 'Ë' => '', + 'Ì' => '', 'Í' => '', 'Î' => '', 'Ï' => '', 'Ð' => '', 'Ñ' => '', 'Ò' => '', + 'Ó' => '', 'Ô' => '', 'Õ' => '', 'Ö' => '', 'Ø' => '', 'Ù' => '', 'Ú' => '', + 'Û' => '', 'Ü' => '', 'Ý' => '', 'ß' => '', ' ' => '', 'á' => '', 'â' => '', + 'ã' => '', 'ä' => '', 'å' => '', 'æ' => '', 'ç' => '', 'è' => '', 'é' => '', + 'ê' => '', 'ë' => '', 'ì' => '', 'í' => '', 'î' => '', 'ï' => '', 'ð' => '', + 'ñ' => '', 'ò' => '', 'ó' => '', 'ô' => '', 'õ' => '', 'ö' => '', 'ø' => '', + 'ù' => '', 'ú' => '', 'û' => '', 'ü' => '', 'ý' => '', 'ÿ' => '', "¿" => '', + '¼' => '', '½' => '', '¾' => '', ' ' => '', '' => '', '' => '', '¡' => '', '­' => '-', + '“' => '"', '”' => '"', '–' => '-', "\n" => ' ', "\r" => ' ', '’' => "'", '' => '"', + + '¿' => '', 'Æ' => '', 'Á' => '', 'Â' => '', 'À' => '', + 'Å' => '', 'Ã' => '', 'Ä' => '', 'Ç' => '', 'Ð' => '', + 'É' => '', 'Ê' => '', 'È' => '', 'Ë' => '', 'Í' => '', + 'Î' => '', 'Ì' => '', 'Ï' => '', 'Ñ' => '', 'Ó' => '', + 'Ô' => '', 'Ò' => '', 'Ø' => '', 'Õ' => '', 'Ö' => '', + 'Ú' => '', 'Û' => '', 'Ù' => '', 'Ü' => '', 'Ý' => '', + 'á' => '', 'â' => '', 'æ' => '', 'à' => '', 'å' => '', + 'ã' => '', 'ä' => '', 'ç' => '', 'é' => '', 'ê' => '', + 'è' => '', 'ð' => '', 'ë' => '', '½' => '', '¼' => '', + '¾' => '', 'í' => '', 'î' => '', '¡' => '', 'ì' => '', + '¿' => '', 'ï' => '', '—' => '', 'µ' => '', '–' => '', + 'ñ' => '', 'ó' => '', 'ô' => '', 'ò' => '', 'ø' => '', + 'õ' => '', 'ö' => '', '"' => '"', '­' => '', 'ß' => '', + 'ú' => '', 'û' => '', 'ù' => '', 'ü' => '', 'ý' => '', + '¥' => '', 'ÿ' => '', '—' => '-', "\n" => ' ', "\r" => ' '); + + return strtr($text, $dict); + } + +} +?> \ No newline at end of file diff --git a/app/controllers/imageviewer_controller.php b/app/controllers/imageviewer_controller.php new file mode 100644 index 0000000..7d50814 --- /dev/null +++ b/app/controllers/imageviewer_controller.php @@ -0,0 +1,28 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class ImageviewerController extends AppController { + function __construct() { + $this->name = 'imageviewer'; + parent::__construct(); + } + + function index() { + } +} +?> \ No newline at end of file diff --git a/app/controllers/login_controller.php b/app/controllers/login_controller.php new file mode 100644 index 0000000..9c705f0 --- /dev/null +++ b/app/controllers/login_controller.php @@ -0,0 +1,149 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class LoginController extends AppController { + function __construct() { + $this->base_dir = APP_DIR; + $this->name = 'login'; + $this->page_title = 'Login'; + parent::__construct(); + } + + function index() { + $this->login(); + } + + function login() { + NDebug::debug('Redirecting ' . $_SERVER['REMOTE_ADDR'] . ' to login to nterchange.', N_DEBUGTYPE_AUTH); + $auth = new NAuth(); + $auth->start(); + + $username = $auth->username; + $status = $auth->status; + + $form = new NQuickForm('login_form', 'post', preg_replace('/logout=1[\&]?/', '', $_SERVER['REQUEST_URI'])); + $form->setDefaults(array('username'=>$username)); + if (isset($_GET['logout']) && $_GET['logout'] == 1) { + $form->addElement('cmsalert', 'logout_header', 'You have signed out. Sign back in to continue.'); + } else { + if ($status < 0 &&!empty($username)) { + $form->addElement('cmserror', 'login_status', $auth->statusMessage($status)); + } else { + $form->addElement('cmsalert', 'login_status', 'Please sign in and you will be sent right along.'); + } + } + $form->addElement('text', 'username', 'Username', array('maxlength'=>32, 'style'=>'width:300px;')); + $form->addElement('password', 'password', 'Password', array('maxlength'=>32, 'style'=>'width:150px;')); + // $form->addElement('checkbox', 'remember', null, 'Remember me for 2 weeks.'); + $form->addElement('submit', 'login', 'Sign In'); + $referer = isset($_GET['_referer'])?urlencode($_GET['_referer']):urlencode('/' . $this->base_dir); + $form->addElement('hidden', '_referer', $referer); + + if ($auth->checkAuth()) { + NDebug::debug('Logged ' . $_POST['username'] . ' from ' . $_SERVER['REMOTE_ADDR'] . ' in to nterchange.', N_DEBUGTYPE_AUTH); + // Log this in the audit trail. + $user_id = $auth->currentUserID(); + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>'users', 'asset_id'=>$user_id, 'action_taken'=>AUDIT_ACTION_LOGIN)); + unset($audit_trail); + // Redirect to the page requested. + header('Location:' . urldecode($referer)); + exit; + } + + $content = $form->toHTML(); + $this->set(array('MAIN_CONTENT'=>$content, 'username'=>$username, 'status'=>$status)); + $this->auto_render = false; + $this->render(array('layout'=>'login')); + } + + /** + * forgot - I forgot my password and need to reset it. Takes an email address and + * sends a confirmation email with a random token to that address. + * + * @return void + **/ + function forgot() { + $form = new NQuickForm('reset_password', 'post'); + $form->addElement('text', 'email', 'Email Address', array('maxlength'=>32, 'style'=>'width:300px;')); + $form->addElement('submit', 'reset_password', 'Reset Password'); + $form->addRule('email', 'You need to enter an email address.', 'required', null, 'client'); + $form->addRule('email', 'The email does not appear to be the correct format', 'email', null, 'client'); + if ($form->validate()) { + $vals = $form->exportValues(); + if (isset($vals['email'])) { + $cms_auth = NModel::factory('cms_auth'); + // Set the token - then send the email. + if ($result = $cms_auth->setConfirmationToken($vals['email'])) { + // Send the confirmation email. + $user = NController::factory('users'); + $user->sendConfirmationEmail($vals['email']); + } + } + // TODO: Put this into the template and out of here. + if ($result == true) { + $content = '

We have sent you a confirmation - please check your email and follow the instructions.

'; + } else { + $content = '

There was a problem - please click back and enter your email address again.

'; + } + $this->set(array('MAIN_CONTENT'=>$content, 'forgot'=>'true')); + } else { + $content = $form->toHTML(); + $this->set(array('MAIN_CONTENT'=>$content, 'forgot'=>'true')); + } + $this->auto_render = false; + $this->render(array('layout'=>'login')); + } + + /** + * confirmPasswordReset - Takes a token passed in the get string, verifies it and resets + * the corresponding password for that particular email address. + * + * @return void + **/ + function confirmPasswordReset() { + // Verify the token + $passed_token = $_GET['token']; + $cms_auth = NModel::factory('cms_auth'); + $cms_auth->confirmation_token = $passed_token; + // If it checks out, then send out the new password. + if ($cms_auth->find()) { + while ($cms_auth->fetch()) { + // If it's there - grab the email. + $email = $cms_auth->email; + // Set the confirmation_token to NULL + $cms_auth->confirmation_token = 'NULL'; + $cms_auth->save(); + // Reset it and send it out. + $cms_auth->resetPassword($email); + $content = 'The password was reset and emailed.'; + } + } else { + $content = 'There was a problem - please try again.'; + } + $this->set(array('MAIN_CONTENT'=>$content)); + $this->auto_render = false; + $this->render(array('layout'=>'login')); + } + +} +?> diff --git a/app/controllers/memcache_controller.php b/app/controllers/memcache_controller.php new file mode 100644 index 0000000..d48dd6d --- /dev/null +++ b/app/controllers/memcache_controller.php @@ -0,0 +1,41 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since Version 3.1.17 + * @todo Expose this if you're actually running a memcached server. + */ +class MemcacheController extends AppController { + function __construct() { + $this->login_required = true; + parent::__construct(); + } + + /** + * index - Just show the memcache server's statistics. + * + * @return void + **/ + function index() { + $this->auto_render = true; + $memcache_obj = new Memcache; + $memcache_obj->addServer(MEMCACHED_SERVER, MEMCACHED_SERVER_PORT); + $stats = $memcache_obj->getExtendedStats(); + varDump($stats); + } +} +?> \ No newline at end of file diff --git a/app/controllers/nterchange_controller.php b/app/controllers/nterchange_controller.php new file mode 100644 index 0000000..7993bed --- /dev/null +++ b/app/controllers/nterchange_controller.php @@ -0,0 +1,73 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class nterchangeController extends AppController { + var $base_dir = APP_DIR; + function __construct() { + if (defined('ADMIN_URL') && constant('ADMIN_URL') != false && is_string(ADMIN_URL) && preg_match('|^/' . APP_DIR . '/|', $_SERVER['REQUEST_URI'])) { + if (!preg_match('|http[s]?://' . $_SERVER['SERVER_NAME'] . '|', ADMIN_URL)) { + $loc = preg_replace('|/$|', '', ADMIN_URL) . $_SERVER['REQUEST_URI']; + // if you don't want a 404 and want to redirect instead, comment out this line + die(NDispatcher::error404()); + header('Location:' . $loc); + exit; + } + } + if (is_null($this->name)) + $this->name = 'nterchange'; + if (is_null($this->base_view_dir)) + $this->base_view_dir = BASE_DIR; + if (!defined('IN_NTERCHANGE')) define('IN_NTERCHANGE', preg_match('|^/' . APP_DIR . '|', NServer::env('REQUEST_URI'))?true:false); + parent::__construct(); + } + + function navigation($current_section) { + if (!isset($this->_auth)) return; + $current_user_level = $this->_auth->getAuthData('user_level'); + // need to loop through other constructors and see + // if they belong in the navigation tabs + $navigation = array(); + $navigation[] = array('title'=>'Dashboard', 'controller'=>'dashboard', 'class'=>''); + $navigation[] = array('title'=>'Site Admin', 'controller'=>'site_admin', 'class'=>''); + if ($current_user_level >= N_USER_ADMIN) { + $navigation[] = array('title'=>'Content', 'controller'=>'content', 'class'=>''); + } + if (SITE_WORKFLOW) { + $navigation[] = array('title'=>'Workflow', 'controller'=>'workflow_group', 'class'=>''); + } + $navigation[] = array('title'=>'Admin', 'controller'=>'admin', 'class'=>'right'); + if ($current_user_level < N_USER_ADMIN) { + $navigation[] = array('title'=>'User', 'controller'=>'users', 'class'=>'right'); + } + foreach ($navigation as $k=>$nav) { + $ctrl = &NController::factory($nav['controller']); + $ctrl->_auth = &$this->_auth; + if (!$ctrl || !$ctrl->checkUserLevel()) { + unset($navigation[$k]); + continue; + } + if ($this->name == $ctrl->name || is_a($this, get_class($ctrl))) { + $navigation[$k]['class'] .= ($navigation[$k]['class']?' ':'') . 'current'; + } + } + return $navigation; + } +} diff --git a/app/controllers/page_content_controller.php b/app/controllers/page_content_controller.php new file mode 100644 index 0000000..510694c --- /dev/null +++ b/app/controllers/page_content_controller.php @@ -0,0 +1,732 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageContentController extends nterchangeController { + function __construct() { + $this->name = 'page_content'; + parent::__construct(); + $this->login_required = true; + $this->public_actions = array('select_content_type', 'add_new_content', 'add_existing_content', 'remove_content', 'reorder'); + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + $this->base_view_dir = ROOT_DIR; + } + + function selectContentType($parameter) { + $page_model = &$this->loadModel('page'); + $page_model->get($parameter); + // instantiate form, + include_once 'n_quickform.php'; + $form = new NQuickForm('select_content_type'); + $values = $form->getSubmitValues(); + + if(isset($values['template_container_id'])){ + $template_container_id = $values['template_container_id']; + } else { + if(isset($this->params['template_container_id'])){ + $template_container_id = $this->params['template_container_id']; + } else { + $template_container_id = false; + } + } + $form->addElement('header', null, 'Do you wish to add new or existing content to the "' . $page_model->title . '" page?'); + // next action + $options = array('addnewcontent'=>'New Content', 'addexistingcontent'=>'Existing Content'); + $form->addElement('select', '_action', 'Select', $options); + // assets for this container + $model = &$this->loadModel('cms_asset_template'); + $pk = $model->primaryKey(); + $model->page_template_container_id = $template_container_id; + $options = array(); + if ($model->find(array('join'=>'INNER JOIN cms_asset_info ON cms_asset_info.asset=' . $model->tableName() . '.asset'))) { + while ($model->fetch()) { + $options[$model->asset] = $model->asset_name; + } + } + $form->addElement('select', 'asset', 'Asset', $options); + // hidden values + $form->addElement('hidden', 'page_id', $parameter); + $form->addElement('hidden', 'template_container_id', $template_container_id); + if (isset($this->params['_referer'])) { + $form->addElement('hidden', '_referer', urlencode($this->params['_referer'])); + } + // finish up + $form->addElement('submit', '__submit__', 'go'); + // rules + $form->addRule('asset', '', 'required'); + $form->addRule('template_container_id', '', 'required'); + // validation + if ($form->validate()) { + $values = $form->exportValues(); + $params = array('asset'=>$this->params['asset'], 'template_container_id'=>$this->params['template_container_id']); + if (isset($this->params['_referer'])) { + $params['_referer'] = $this->params['_referer']; + } + $this->redirectTo($values['_action'], $parameter, $params); + } + unset($page_model); + $this->auto_render = false; + $this->page_title = 'Select Content Type'; + $this->set('form', $form->toHTML()); + $this->render(array('action'=>'form', 'layout'=>'plain')); + } + + function addNewContent($parameter, $load_model_content=false) { + $page_model = &$this->loadModel('page'); + $page_model->get($parameter); + $template_container_id = isset($this->params['template_container_id'])?$this->params['template_container_id']:false; + $asset = isset($this->params['asset'])?$this->params['asset']:false; + $asset_controller = &NController::singleton($asset); + $asset_controller->_auth = new NAuth(); + + // load the model layer with info + $asset_model = &$asset_controller->getDefaultModel(); + + // create the form + include_once 'controller/form.php'; + $cform = new ControllerForm($asset_controller, $asset_model); + $form = &$cform->getForm(); + // check for workflow + if (SITE_WORKFLOW) { + // get the users rights and bit compare them below + $workflow = &NController::factory('workflow'); + $user_rights = $workflow->getWorkflowUserRights($page_model); + $workflow_group = &$workflow->getWorkflowGroup($page_model); + if ($workflow_group && !($user_rights & WORKFLOW_RIGHT_EDIT)) { + // they don't belong here - go to the dashboard + header('Location:/' . APP_DIR . '/dashboard'); + } else if ($user_rights & WORKFLOW_RIGHT_EDIT) { + $form->insertElementBefore(NQuickForm::createElement('submit', '__submit_workflow__', 'Start Workflow'), '__submit__'); + $form->removeElement('__submit__'); + } + unset($workflow); + } + // timed content + $form->addElement('header', null, 'Make it timed content?'); + $timed_options = array('format'=>'Y-m-d H:i', 'minYear'=>date('Y'), 'maxYear'=>date('Y')+4, 'addEmptyOption'=>true); + $form->addElement('date', 'timed_start', 'Timed Start', $timed_options); + $form->addElement('date', 'timed_end', 'Timed End', $timed_options); + $form->addElement('submit', '__submit_timed__', 'Add Scheduled Content'); + // page_content values + $form->addElement('hidden', 'template_container_id', $template_container_id); + $form->addElement('hidden', 'asset', $asset); + if (isset($this->params['_referer'])) { + $form->addElement('hidden', '_referer', urlencode($this->params['_referer'])); + } + // assign the info and render + $asset_controller->base_dir = APP_DIR; + $assigns = array(); + $table = $asset_model->table(); + $fields = $asset_model->fields(); + if ($form->validate()) { + $values = $form->getSubmitValues(); + if (in_array('cms_created', $fields)) { + $asset_model->cms_created = $asset_model->now(); + } + if (in_array('cms_modified', $fields)) { + $asset_model->cms_modified = $asset_model->now(); + } + // set the user id if it's applicable and available + if (in_array('cms_modified_by_user', $fields)) { + $asset_model->cms_modified_by_user = isset($asset_controller->_auth)?$asset_controller->_auth->currentUserId():0; + } + $referer = isset($values['_referer'])?$values['_referer']:false; + if ($referer) { + // cheat and remove the referer from the form oject so it doesn't redirect + if (isset($form->_submitValues['_referer'])) unset($form->_submitValues['_referer']); + } + $success = $form->process(array($cform, 'processForm')); + $asset_id = $asset_model->{$asset_model->primaryKey()}; + if ($success) { + $values = $form->exportValues(); + $model = &$this->loadModel($this->name); + $workflow_active = false; + $workflow_required = false; + if (SITE_WORKFLOW) { + $workflow = &NController::factory('workflow'); + if ($workflow_group_model = $workflow->getWorkflowGroup($page_model)) { + $workflow_required = true; + } + if ($workflow_required && isset($values['__submit_workflow__'])) { + $workflow_active = true; + } + } + $model->page_id = $parameter; + $model->page_template_container_id = $values['template_container_id']; + $model->content_asset = $values['asset']; + $model->content_asset_id = $asset_id; + // prep the timed content values if they exist + if (isset($values['timed_start'])) { + $values['timed_start'] = NDate::arrayToDate($values['timed_start']); + $values['timed_start'] = NDate::convertTimeToUTC($values['timed_start']); + } + if (isset($values['timed_end'])) { + $values['timed_end'] = NDate::arrayToDate($values['timed_end']); + $values['timed_end'] = NDate::convertTimeToUTC($values['timed_end']); + } + // + if (!$workflow_active) { + if (isset($values['timed_start'])) $model->timed_start = $values['timed_start']; + if (isset($values['timed_end'])) $model->timed_end = $values['timed_end']; + } + if ($workflow_required) { + $model->cms_workflow = 1; + } + $model->cms_created = $model->now(); + $model->cms_modified = $model->now(); + $model->cms_modified_by_user = $this->_auth->currentUserID(); + if ($workflow_active) { + // set page_content cms_workflow to 1 so it can't show up + $model->cms_workflow = 1; + } + $model->insert(); + // do the workflow stuff if appropriate + if ($workflow_active) { + $page_content_id = $model->{$model->primaryKey()}; + $workflow_values = array(); + $workflow_values['page_content_id'] = $page_content_id; + $workflow_values['workflow_group_id'] = $workflow_group_model->{$workflow_group_model->primaryKey()}; + // add timed content + if (isset($values['timed_start'])) $workflow_values['timed_start'] = $values['timed_start']; + if (isset($values['timed_end'])) $workflow_values['timed_end'] = $values['timed_end']; + $workflow->saveWorkflow($workflow_values, WORKFLOW_ACTION_ADDNEW, $asset_controller); + // set page_content cms_workflow to 1 so it can't show up + } + // delete the page cache + $page = &NController::singleton('page'); + $page->deletePageCache($model->page_id); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$asset_controller->name, 'asset_id'=>$asset_model->{$asset_model->primaryKey()}, 'action_taken'=>AUDIT_ACTION_CONTENT_ADDNEW, 'page_content_id'=>$model->{$model->primaryKey()}, 'page_id'=>$model->page_id)); + unset($audit_trail); + } + unset($page); + unset($model); + } + if (isset($this->params['_referer']) && $this->params['_referer']) { + $referer = urldecode($this->params['_referer']); + } else { + include_once 'view/helpers/url_helper.php'; + $referer = urlHelper::urlFor($this, array('controller'=>'page', 'action'=>'surftoedit', 'id'=>$parameter)); + } + header('Location:' . $referer); + exit; + } else { + if ($asset_model) { + $this->set(array('form'=>$form->toHTML(), 'asset'=>$asset_controller->name, 'asset_name'=>Inflector::humanize($asset_controller->name))); + } + } + $this->auto_render = false; + $this->page_title = 'Add New Content to "' . $page_model->title . '"'; + $this->render(array('action'=>'form', 'layout'=>'default')); + } + + function addExistingContent($parameter) { + $page_model = &$this->loadModel('page'); + $page_model->get($parameter); + $template_container_id = isset($this->params['template_container_id'])?$this->params['template_container_id']:false; + $asset = isset($this->params['asset'])?$this->params['asset']:false; + // instantiate form + include_once 'n_quickform.php'; + $form = new NQuickForm(); + $values = $form->getSubmitValues(); + $form->addElement('header', null, 'Add "' . Inflector::humanize($asset) . '" content to the "' . $page_model->title . '" page'); + $asset_controller = &NController::factory($asset); + $asset_model = &NModel::factory($asset); + $pk = $asset_model->primaryKey(); + $records = array(); + if ($asset_model->find()) { + while ($asset_model->fetch()) { + $records[$asset_model->$pk] = $asset_model->cms_headline; + } + } + unset($asset_model); + // add asset select + $options = defined('SITE_WORKFLOW') && SITE_WORKFLOW?array():array('size'=>10, 'multiple'=>'multiple'); + $form->addElement('select', 'asset_id', Inflector::humanize($asset), $records, $options); + // hidden fields + $form->addElement('hidden', 'asset', $asset); + $form->addElement('hidden', 'template_container_id', $template_container_id); + if (isset($this->params['_referer'])) { + $form->addElement('hidden', '_referer', urlencode($this->params['_referer'])); + } + // finish up + $form->addElement('submit', '__submit__', 'Add Content'); + // rules + defined('SITE_WORKFLOW') && SITE_WORKFLOW?$form->addRule('asset_id', 'You must select a record.', 'required'):$form->addGroupRule('asset_id', 'You must select a record.', 'required'); + $form->addRule('asset', '', 'required'); + $form->addRule('template_container_id', '', 'required'); + // check for workflow + $user_rights = 0; + if (SITE_WORKFLOW) { + // get the users rights and bit compare them below + $workflow = &NController::factory('workflow'); + $user_rights = $workflow->getWorkflowUserRights($page_model); + if ($workflow_group_model = &$workflow->getWorkflowGroup($page_model)) { + if (!($user_rights & WORKFLOW_RIGHT_EDIT)) { + // they don't belong here - go to the dashboard + header('Location:/' . APP_DIR . '/dashboard'); + } else if ($user_rights & WORKFLOW_RIGHT_EDIT) { + $form->insertElementBefore(NQuickForm::createElement('submit', '__submit_workflow__', 'Start Workflow'), '__submit__'); + $form->removeElement('__submit__'); + } + } + unset($workflow); + } + $form->addElement('header', null, 'Make it timed content?'); + $timed_options = array('format'=>'Y-m-d H:i', 'minYear'=>date('Y'), 'maxYear'=>date('Y')+4, 'addEmptyOption'=>true); + $form->addElement('date', 'timed_start', 'Timed Start', $timed_options); + $form->addElement('date', 'timed_end', 'Timed End', $timed_options); + if (!$user_rights) { + $form->addElement('submit', '__submit_timed__', 'Add Scheduled Content'); + } else { + $form->addElement('submit', '__submit_workflow__', 'Start Workflow with Scheduled Content'); + } + if ($form->validate()) { + $values = $form->exportValues(); + $model = &$this->loadModel($this->name); + $workflow_active = false; + if (SITE_WORKFLOW) { + $workflow = &NController::factory('workflow'); + // check if this content is on any other page. + // if it is, if either pages are part of a workflow group, we need to copy the content (go to addnewcontent with notice) + // if neither do, then go ahead + $asset_model = &$asset_controller->loadModel($asset_controller->name); + $asset_model->get($values['asset_id']); + $other_page = &$this->getContentPage($asset_controller); + if ($other_page) { + $owned_content = false; + if ($workflow_group_model = &$workflow->getWorkflowGroup($page_model)) { + $owned_content = true; + } else { + if ($workflow_group_model = &$workflow->getWorkflowGroup($other_page)) { + $owned_content = true; + } + } + // if the content is already connected somewhere and one of the pages belongs to a workflow_group, then addNewContent with preloaded content + if ($owned_content) { + if (isset($values['__submit__'])) unset($values['__submit__']); + if (isset($values['__submit_workflow__'])) unset($values['__submit_workflow__']); + $this->redirectTo('copy_existing_content', $parameter, $values); + exit; + } + } + if (isset($values['__submit_workflow__']) && $values['__submit_workflow__']) { + $workflow = &NController::factory('workflow'); + if ($workflow_group_model = $workflow->getWorkflowGroup($page_model)) { + $workflow_active = true; + } + } + } + $model->page_id = $parameter; + if (SITE_WORKFLOW && isset($values['__submit_workflow__']) && $values['__submit_workflow__']) { + $model->cms_workflow = 1; + } + $model->page_template_container_id = $values['template_container_id']; + $model->content_asset = $values['asset']; + // set the timed values + $timed_start = null; + $timed_end = null; + include_once 'n_date.php'; + if (isset($values['timed_start'])) { + $timed_start = NDate::arrayToDate($values['timed_start']); + $timed_start = NDate::convertTimeToUTC($timed_start); + unset($values['timed_start']); + } + if (isset($values['timed_end'])) { + $timed_end = NDate::arrayToDate($values['timed_end']); + $timed_end = NDate::convertTimeToUTC($timed_end); + unset($values['timed_end']); + } + if (!$workflow_active) { + $table = $model->table(); + $def = $table['timed_start']; + if (NDate::validDateTime($timed_start, $def)) { + $model->timed_start = $timed_start; + } else { + $model->timed_start = N_DAO_NOTNULL & $def?$timed_start:'null'; + } + $def = $table['timed_end']; + if (NDate::validDateTime($timed_end, $def)) { + $model->timed_end = $timed_end; + } else { + $model->timed_end = N_DAO_NOTNULL & $def?$timed_end:'null'; + } + } + $model->cms_created = $model->now(); + $model->cms_modified = $model->now(); + $model->cms_modified_by_user = $this->_auth->currentUserID(); + if (!is_array($values['asset_id'])) { + $values['asset_id'] = array($values['asset_id']); + } + foreach ($values['asset_id'] as $asset_id) { + $model->content_asset_id = $asset_id; + $model->insert(); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$asset_controller->name, 'asset_id'=>$asset_id, 'action_taken'=>AUDIT_ACTION_CONTENT_ADDEXISTING, 'page_content_id'=>$model->{$model->primaryKey()}, 'page_id'=>$model->page_id)); + unset($audit_trail); + } + } + if ($workflow_active) { + $asset_controller = &NController::factory($values['asset']); + $asset_controller->_auth = new NAuth(); + $asset_model = &$asset_controller->getDefaultModel(); + $asset_model->get($values['asset_id'][0]); + $workflow_values = array(); + $workflow_values['page_content_id'] = $model->{$model->primaryKey()}; + $workflow_values['workflow_group_id'] = $workflow_group_model->{$workflow_group_model->primaryKey()}; + // add timed content + $workflow_values['timed_start'] = $timed_start; + $workflow_values['timed_end'] = $timed_end; + $workflow->saveWorkflow($workflow_values, WORKFLOW_ACTION_ADDEXISTING, $asset_controller); + } + // delete the page cache + $page = &NController::singleton('page'); + $page->deletePageCache($model->page_id); + unset($page); + // set up the referer + if (isset($this->params['_referer']) && $this->params['_referer']) { + $referer = urldecode($this->params['_referer']); + } else { + include_once 'view/helpers/url_helper.php'; + $referer = urlHelper::urlFor($this, array('controller'=>'page', 'action'=>'surftoedit', 'id'=>$parameter)); + } + header('Location:' . $referer); + exit; + } + $this->auto_render = false; + $this->page_title = 'Add Existing Content to "' . $page_model->title . '"'; + $this->set(array('title'=>'Select Content', 'form'=>$form->toHTML())); + $this->render(array('action'=>'form', 'layout'=>'plain')); + unset($page_model); + } + + function copyExistingContent($parameter) { + $params = $this->params; + if (!isset($params['asset']) || !isset($params['asset_id']) || !isset($params['template_container_id'])) { + header('Location:/' . APP_DIR . '/'); + } + $asset_controller = &NController::singleton($params['asset']); + $asset_model = &$asset_controller->getDefaultModel(); + $asset_model->get($params['asset_id']); + $asset_model->{$asset_model->primaryKey()} = null; + $asset_model->cms_headline = null; + $this->flash->set('notice', 'The content you chose already belongs to another Workflow Group.
We have made a copy of the content for you.
Please enter a new headline to connect the content to your page.'); + $this->flash->now('notice'); + $this->addNewContent($parameter, true); + } + + function removeContent($parameter, $redirect=true, $timed_remove=false) { + $model = &$this->getDefaultModel(); + $referer = (isset($this->params['_referer']) && $this->params['_referer'])?$this->params['_referer']:false; + if ($model->get($parameter)) { + // check for workflow + // if it's a timed remove, the timed portion went through workflow, so it's okay + if (SITE_WORKFLOW && $timed_remove == false) { + // get the users rights and bit compare them below + $workflow = &NController::factory('workflow'); + $page_model = &$model->getLink('page_id', 'page'); + $user_rights = $workflow->getWorkflowUserRights($page_model); + if ($workflow_group_model = &$workflow->getWorkflowGroup($page_model)) { + if (!($user_rights & WORKFLOW_RIGHT_EDIT)) { + // they don't belong here - go to the dashboard + header('Location:/' . APP_DIR . '/dashboard'); + exit; + } + $asset_controller = &NController::factory($model->content_asset); + $asset_controller->_auth = new NAuth(); + $asset_model = &$asset_controller->getDefaultModel(); + $asset_model->get($model->content_asset_id); + // workflow values for saveWorkflow + $workflow_values = array(); + $workflow_values['page_content_id'] = $model->{$model->primaryKey()}; + $workflow_values['workflow_group_id'] = $workflow_group_model->{$workflow_group_model->primaryKey()}; + // save the workflow + $workflow->saveWorkflow($workflow_values, WORKFLOW_ACTION_REMOVE, $asset_controller); + if ($redirect) { + include_once 'view/helpers/url_helper.php'; + $referer = isset($this->params['referer'])?urldecode($this->params['referer']):urlHelper::urlFor($this, array('controller'=>'page', 'action'=>'surftoedit', 'id'=>$page_model->{$page_model->primaryKey()})); + header('Location:' . $referer); + exit; + } + } + unset($workflow); + } + include_once 'view/helpers/url_helper.php'; + $page_id = $model->page_id; + if (!$referer) { + $referer = urlHelper::urlFor($this, array('controller'=>'page', 'action'=>'surftoedit', 'id'=>$page_id)); + } + // delete the page cache + $page = &NController::singleton('page'); + $page->deletePageCache($model->page_id); + unset($page); + $audit_trail_array = array('asset'=>$model->content_asset, 'asset_id'=>$model->content_asset_id, 'action_taken'=>AUDIT_ACTION_CONTENT_REMOVE, 'page_content_id'=>$model->{$model->primaryKey()}, 'page_id'=>$model->page_id); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail just before the delete or we lose the info + if ($timed_remove == false) { + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert($audit_trail_array); + // Bit of an ugly hack, but I didn't want to mess with the controller. + // The model doesn't require authentication, so we can force it through when + // we're removing timed_content auto-magically. + } elseif ($timed_remove == true) { + $audit_trail = &NModel::factory('cms_audit_trail'); + $audit_trail->insert_audit_trail($audit_trail_array); + } + unset($audit_trail); + } + unset($audit_trail_array); + // delete the page_content record + $deleted = $model->delete(); + // if delete was successful and there is an unsubmitted workflow, then cascade delete the workflow + if ($timed_remove == false && $deleted && SITE_WORKFLOW && $workflow_model = &$this->loadModel('workflow')) { + $workflow_model->page_id = $page_id; + $workflow_model->asset = $model->content_asset; + $workflow_model->asset_id = $model->content_asset_id; + $workflow_model->submitted = 0; + $workflow_model->parent_workflow = 0; + $workflow_model->cms_modified_by_user = $this->_auth->currentUserID(); + if ($workflow_model->find()) { + while ($workflow_model->fetch()) { + $workflow_model->delete(); + } + } + unset($workflow_model); + } + unset($model); + } + if ($redirect) { + header('Location:' . $referer); + exit; + } + } + + + function show($parameter, $layout=true) { + $this->auto_render = false; + $this->base_dir = APP_DIR; + $model = &$this->getDefaultModel(); + if ($model && $model->get($parameter)) { + $this->convertDateTimesToClient($model); + $page_controller = &NController::singleton('page'); + + if ($this->checkUserLevel()) { + $page_controller->edit = true; + $page_controller->page_edit_allowed = true; + $page_controller->content_edit_allowed = true; + } + + $html = $page_controller->getContainerContent($model->page_id, $model->page_template_container_id, $model->id); + } else { + $html = 'The specified record could not be found.'; + } + print trim($html); + } + + + function reorder() { + $id = isset($this->params['id'])?(int) $this->params['id']:false; + if (!$id) { + return; + } + $before = isset($this->params['before'])?(int) $this->params['before']:false; + if ($model = &$this->getDefaultModel() && $model->get($id)) { + $pk = $model->primaryKey(); + $page_content_model = &NModel::factory($this->name); + $page_content_model->page_id = $model->page_id; + $page_contents = array(); + if ($page_content_model->find()) { + $before_found = false; + $item = null; + while ($page_content_model->fetch()) { + if ($page_content_model->$pk == $id) { + // pull the "id" record out of the array + $item = clone($page_content_model); + } else { + $page_contents[] = clone($page_content_model); + } + } + if (!$before) { // if there is no before, then the item goes last + $page_contents[] = $item; + } else { // loop through until you find "before" and then splice the "id" in front of it + foreach ($page_contents as $key=>$page_content) { + if ($page_content->$pk == $before) { + array_splice($page_contents, $key, 1, array($item, $page_content)); + break; + } + } + } + $i = 0; + foreach ($page_contents as $key=>$page_content) { + $page_content->content_order = $i; + $page_content->save(false,false); // second false prevents converting time to UTC (unnecessary for a reorder) + $i++; + } + } + $page = &NController::singleton('page'); + $page->deletePageCache($model->page_id); + unset($page); + unset($page_contents); + unset($page_content_model); + unset($model); + } + } + + function &getContentPage(&$asset_controller) { + if (!$asset_controller) return false; + $asset_model = &$asset_controller->loadModel($asset_controller->name); + if (!$asset_model) return false; + $pk = $asset_model->primaryKey(); + $model = &NModel::factory($this->name); + $model->content_asset = $asset_controller->name; + $model->content_asset_id = $asset_model->$pk; + if ($model->find(null, true)) { + $page_model = &$model->getLink('page_id', 'page'); + unset($model); + return $page_model; + } + unset($model); + $model = false; + return $model; + } + + function changeTemplate($page_id, $to_template_id) { + $page_id = (int)$page_id; + $to_template_id = (int)$to_template_id; + if (!$page_id || !$to_template_id) { + // TODO: add a log error + return false; + } + + // load the container_vars + $tc_model = &NModel::singleton('page_template_containers'); + $tc_pk = $tc_model->primaryKey(); + $tc_model->reset(); + $tc_model->page_template_id = $to_template_id; + $containers = array(); + if ($tc_model->find()) { + while ($tc_model->fetch()) { + $containers[$tc_model->$tc_pk] = $tc_model->container_var; + } + } + + // grab records attached to the old template + $model = &$this->getDefaultModel(); + $model->reset(); + $model->page_id = $page_id; + $conditions = ''; + foreach ($containers as $container_id=>$container_var) { + $conditions .= ($conditions?' AND ':'') . 'page_template_container_id != ' . $container_id; + } + $old_contents = $model->find(array('conditions'=>$conditions))?$model->fetchAll():array(); + if (empty($old_contents)) return true; + + // loop through the old content. If the new template has the same + // container_var and isn't already connected to that template_container, + // then update the record + foreach ($old_contents as $content) { + // grab the old template_container for the content + $tc_model->reset(); + if ($tc_model->get($content->page_template_container_id)) { + if ($new_container_id = array_search($tc_model->container_var, $containers)) { + // check if the same content is already connected to the new template_container + $model->reset(); + $model->page_id = $page_id; + $model->content_asset = $content->content_asset; + $model->content_asset_id = $content->content_asset_id; + $model->page_template_container_id = $new_container_id; + if ($model->find()) { + continue; + } + // if not, update the content to the new container_id + $content->page_template_container_id = $new_container_id; + $content->update(); + } + } + } + return true; + } + + /* + / Grab all of the content_asset_ids for a particular asset_name on a + / particular page in a particular container. + / Used to create RSS feeds from a blog page, also to show last + / few records connected to a page on another. + */ + function getAssetContainerPageItems ($page_template_container_id, $page_id, $asset_name) { + $model = &NModel::factory('page_content'); + $model->page_id = $page_id; + $model->content_asset = $asset_name; + $model->page_template_container_id = $page_template_container_id; + if ($model->find()) { + $records = &$model->fetchAll(true); + $records = $this->_removeTimedContent($records); + unset($model); + $model = &NModel::factory($asset_name); + foreach ($records as $record) { + $model->reset(); + if ($model->get($record['content_asset_id'])) { + $assets[] = $model->toArray(); + } + } + } + return $assets; + } + + // Used with getAssetContainerPageItems to remove any timed content from the + // $assets array. + function _removeTimedContent ($records) { + $cleaned_records = array(); + $time = date("Y-m-d G:i:s", time()); + $time = NDate::convertTimeToUTC($time); + foreach ($records as $record) { + // If they're both null - just add the record and skip the rest. + if(is_null($record['timed_start']) && is_null($record['timed_end']) || ($record['timed_start'] == '0000-00-00 00:00:00')) { + $cleaned_records[] = $record; + continue; + } + if (!is_null($record['timed_start']) && ($record['timed_start'] > $time)) continue; + if (!is_null($record['timed_end']) && ($record['timed_end'] < $time)) continue; + $cleaned_records[] = $record; + } + return $cleaned_records; + } + + // See if an asset is being used in a particular container. + // Return an int. + function checkAssetContainerUsage($asset, $container_id) { + $count = 0; + $model = &NModel::factory($this->name); + $model->content_asset = $asset; + $model->page_template_container_id = $container_id; + if ($model->find()) { + while ($model->fetch()) { + $count++; + } + } + unset ($model); + return $count; + } +} +?> diff --git a/app/controllers/page_controller.php b/app/controllers/page_controller.php new file mode 100755 index 0000000..a94c35f --- /dev/null +++ b/app/controllers/page_controller.php @@ -0,0 +1,1384 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageController extends SiteAdminController { + // ONLY IN THIS CONTROLLER + var $nterchange = false; + var $edit = false; + + // permissions settings + var $page_edit_allowed = false; + var $content_edit_allowed = false; + + var $page_last_modified = 0; + var $view_cache_lifetimes = array(); + + // special settings for page + var $public_actions = array('index', 'menus'); + + function __construct() { + $this->name = 'page'; + // more logins for this controller + $this->login_required[] = 'surftoedit'; + $this->login_required[] = 'preview'; + $this->login_required[] = 'site_admin'; + $this->login_required[] = 'content'; + $this->login_required[] = 'children'; + $this->login_required[] = 'reorder_content'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + parent::__construct(); + } + + function index($parameter) { + $this->page($parameter); + } + + function create($parameter) { + $this->nterchange = true; + parent::create($parameter); + } + + function edit($parameter) { + if (!$this->checkUserLevel()) { + $this->redirectTo(array('site_admin')); + } + $this->nterchange = true; + $this->loadSubnav($parameter); + $model = &$this->getDefaultModel(); + $model->get($parameter); + $this->page_title = 'Page - ' . $model->title; + $this->set('page_title', $model->title); + $this->loadSidebar(); + $model->reset(); + parent::edit($parameter); + } + + /** + * delete - Very bad things can happen if certain pages are deleted, + * here we check the id and throw a nasty error if that happens. + */ + function delete($parameter) { + $protected_pages = NConfig::$protectedPages; + + if (in_array($parameter, $protected_pages)) { + $this->flash->set('notice', 'This content is protected and cannot be deleted.'); + $this->redirectTo(array('page', "edit/$parameter")); + } else { + parent::delete($parameter); + } + } + + function content($parameter) { + if (!$this->checkUserLevel()) { + $this->redirectTo(array('site_admin')); + } + $this->nterchange = true; + $this->auto_render = false; + $this->page_title = 'Page Content'; + $this->loadSubnav($parameter); + $model = &$this->loadModel($this->name); + $page_content = &NController::factory('page_content'); + $page_content_model = &$page_content->loadModel('page_content'); + $pk = $page_content_model->primaryKey(); + $page_content->set('page_id', $parameter); + $content_html = ''; + if ($model->get($parameter)) { + $this->page_title .= ' - ' . $model->title; + $content = array(); + $template_containers = $this->templateContainers($model->page_template_id); + if (!is_array($template_containers)) return null; + foreach ($template_containers as $container) { + if (empty($container)) continue; + $page_content_model->reset(); + $page_content_model->getContainerContent($model->{$model->primaryKey()}, $container['id'], $this->nterchange); + $this->set(array('container'=>$container)); + $content_html .= $this->render(array('action'=>'content_container_title', 'return'=>true)); + $page_content->set('template_container_id', $container['id']); + $page_content->set('no_reorder', true); + $content_html .= $page_content->render(array('action'=>'asset_add', 'return'=>true)); + if ($page_content_model->numRows()) { + $contents = array(); + while ($page_content_model->fetch()) { + $asset_ctrl = &NController::factory($page_content_model->content_asset); + if ($asset_ctrl && $asset_model = &$asset_ctrl->loadModel($asset_ctrl->name) && $asset_model->get($page_content_model->content_asset_id)) { + $content = $asset_model->toArray(); + $content['_asset'] = $asset_ctrl->name; + $content['_asset_name'] = Inflector::humanize($asset_ctrl->name); + $content['page_content_id'] = $page_content_model->$pk; + $contents[] = $content; + } + unset($asset_ctrl); + } + if (empty($contents)) continue; + $this->set('template_container_id', $container['id']); + $this->set('contents', $contents); + $this->set(array('reorder_link'=>count($contents) > 1?true:false)); + $content_html .= $this->render(array('action'=>'content_container', 'return'=>true)); + } + } + $this->set('page_title', $model->title); + $this->loadSidebar(); + } + $this->set('MAIN_CONTENT', $content_html); + $this->render(array('layout'=>'default')); + } + + function children($parameter) { + if (!$this->checkUserLevel()) { + $this->redirectTo(array('site_admin')); + } + $this->nterchange = true; + $this->auto_render = false; + // set view caching to false + $this->view_caching = false; + $this->loadSubnav($parameter); + $model = &$this->getDefaultModel(); + if ($model->get($parameter)) { + $this->page_title = 'Page Children - ' . $model->title; + $children = $model->getChildren($parameter, false, false); + $this->set('page_title', $model->title); + $this->set('parent_id', $parameter); + $this->set('pages', $children); + $this->loadSidebar(); + } + $this->render(array('layout'=>'default', 'action'=>'children_container')); + } + + function loadSidebar() { + $model = &$this->getDefaultModel(); + $surfedit = false; + switch ($this->_auth->getAuthData('user_level')) { + case N_USER_EDITOR: + $surfedit = true; + break; + case N_USER_ADMIN: + case N_USER_ROOT: + $surfedit = true; + break; + } + if (SITE_WORKFLOW) { + $assigns['workflow'] = ''; + $workflow = &NController::singleton('workflow'); + if ($workflow_group_model = &$workflow->getWorkflowGroup($model)) { + $user_rights = $workflow->getWorkflowUserRights($model); + if ($user_rights & WORKFLOW_RIGHT_EDIT) { + $surfedit = true; + } + } + } + $this->set('surfedit', $surfedit); + $this->set('page_id', $model->{$model->primaryKey()}?$model->{$model->primaryKey()}:null); + $this->set('action', $this->action); + $this->set(array('children'=>$this->getChildren(), 'breadcrumbs'=>$this->getBreadcrumbs(false))); + $this->set('SIDEBAR_CONTENT', $this->render(array('action'=>'sidebar', 'return'=>true))); + } + + function reorder($parameter) { + NDebug::debug('Reordering children of page id ' . $parameter, N_DEBUGTYPE_INFO); + if (!$this->checkUserLevel()) { + $this->redirectTo(array('site_admin')); + } + $id = isset($this->params['id'])?(int) $this->params['id']:false; + if (!$id) { + return; + } + $before = isset($this->params['before'])?(int) $this->params['before']:false; + if ($model = &$this->getDefaultModel() && $model->get($id)) { + $pk = $model->primaryKey(); + $page_model = &NModel::factory($this->name); + $page_model->parent_id = $parameter; + $pages = array(); + if ($page_model->find()) { + $before_found = false; + $item = null; + while ($page_model->fetch()) { + if ($page_model->$pk == $id) { + // pull the "id" record out of the array + $item = clone($page_model); + } else { + $pages[] = clone($page_model); + } + } + if (!$before) { // if there is no before, then the item goes last + $pages[] = $item; + } else { // loop through until you find "before" and then splice the "id" in front of it + foreach ($pages as $key=>$page) { + if ($page->$pk == $before) { + array_splice($pages, $key, 1, array($item, $page)); + break; + } + } + } + $i = 0; + foreach ($pages as $key=>$page) { + $page->sort_order = $i; + $page->save(); + $i++; + } + } + $site_admin = &NController::singleton('site_admin'); + if ($model->getParent($id) == $model->getRootNode()) { + // we need to clear all caches + @ob_start(); + $site_admin->clearAllCache(); + @ob_end_clean(); + } else { + $site_admin->deleteCache(); + include_once 'n_cache.php'; + NCache::removeJavascript(); + } + unset($site_admin); + unset($pages); + unset($page_model); + unset($model); + } + } + + function checkUserLevel() { + switch ($this->action) { + case 'surftoedit': + case 'preview': + $this->user_level_required = N_USER_NORIGHTS; + break; + case 'content': + $this->user_level_required = N_USER_EDITOR; + break; + default: + $this->user_level_required = N_USER_ADMIN; + } + return parent::checkUserLevel(); + } + + function viewlist() { + if ($this->flash->exists('notice')) { + $this->flash->keep('notice'); + } + $this->redirectTo(array('site_admin')); + } + + function page($parameter) { + if (!defined('IN_SURFTOEDIT')) define('IN_SURFTOEDIT', $this->nterchange && $this->edit); + $this->base_view_dir = ROOT_DIR; + if (!$parameter) { + $this->do404(); + return; + } + $this->auto_render = false; + // load the model + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + if (!$model->get($parameter)) { // get the page info + // if the page doesn't exist, then 404 + $this->do404(); + return; + } + + if (!$this->nterchange && $model->external_url && preg_match('/^(http[s]?)|(\/)/', $model->external_url)) { + header('Location:' . $model->external_url); + return; + } + + // check if a disclaimer is required + if (defined('SITE_DISCLAIMER') && constant('SITE_DISCLAIMER') && !$this->nterchange && $disclaimer = &NController::factory('disclaimer')) { + $disclaimer->checkDisclaimer($parameter); + } + + // find the action + $action = $this->getTemplate($model->page_template_id); + $action = $action?$action:'default'; + // set up caching + if (!$this->nterchange && defined('PAGE_CACHING') && PAGE_CACHING == true && $model->cache_lifetime != 0) { + // set the view cache values + $this->view_cache_name = 'page' . $parameter . (NServer::env('QUERY_STRING')?':' . md5(NServer::env('QUERY_STRING')):''); + $this->view_caching = true; + $this->view_cache_lifetime = $model->cache_lifetime; + $this->view_cache_lifetimes[] = $model->cache_lifetime; + $this->view_client_cache_lifetime = isset($model->client_cache_lifetime)?$model->client_cache_lifetime:'3600'; + header('Expires:' . gmdate('D, d M Y H:i:s \G\M\T', time() + $this->view_client_cache_lifetime)); + header('Cache-Control:max-age='.$this->view_client_cache_lifetime.', must-revalidate'); + } else { + header('Cache-Control:max-age=0, must-revalidate'); + } + // set the page fields + $this->set($model->toArray()); + // load the page, checking if it's cached if we're not in nterchange + if ($this->nterchange || $this->getParam('mode') == 'print' || !$this->isCached(array('action'=>$action))) { + if (defined('PAGE_CACHING') && PAGE_CACHING == false) { + $this->debug('Cache not created for page for Page ID ' . $model->$pk . ' because PAGE_CACHING is set to false.', N_DEBUGTYPE_CACHE); + } else if ($model->cache_lifetime == 0) { + $this->debug('Cache not created for page for Page ID ' . $model->$pk . ' because caching is turned off for that page.', N_DEBUGTYPE_CACHE); + } else { + if (!$this->nterchange) $this->debug('Created cached page for Page ID ' . $model->$pk . '.', N_DEBUGTYPE_CACHE); + } + $this->page_last_modified = strtotime($model->cms_modified); + + // load up the manual content (site name, breadcrumbs, children, nav, etc.) + $contents['_SITE_NAME_'] = htmlentities(SITE_NAME); + $contents['_EXTERNAL_CACHE_'] = defined('EXTERNAL_CACHE') && constant('EXTERNAL_CACHE')?EXTERNAL_CACHE:false; + $contents['_PAGE_EDIT_'] = ''; + if ($this->checkUserLevel()) { + $this->page_edit_allowed = true; + $this->content_edit_allowed = true; + } + if ($this->nterchange && $this->edit && SITE_WORKFLOW) { + // set up the user's rights on the page + $workflow = &NController::factory('workflow'); + if ($workflow_group_model = &$workflow->getWorkflowGroup($model) && $users = $workflow->getWorkflowUsers($workflow_group_model->{$workflow_group_model->primaryKey()})) { + $contents['_PAGE_EDIT_'] = '
This page is owned by the "' . $workflow_group_model->workflow_title . '" Workflow Group
' . "\n"; + $current_user = $this->_auth->currentUserID(); + $edit = false; + foreach ($users as $user) { + if ($current_user == $user->user_id) { + $edit = true; + } + } + $this->content_edit_allowed = $edit; + $assigns['workflow'] = $workflow_group_model->workflow_title; + $user_rights = $workflow->getWorkflowUserRights($model); + $this->content_edit_allowed = $user_rights & WORKFLOW_RIGHT_EDIT?true:false; + } else { + switch ($this->_auth->getAuthData('user_level')) { + case N_USER_NORIGHTS: + $this->page_edit_allowed = false; + $this->content_edit_allowed = false; + break; + case N_USER_EDITOR: + $this->page_edit_allowed = false; + $this->content_edit_allowed = true; + break; + } + } + unset($workflow); + } + if ($this->edit && $this->page_edit_allowed) { + // $contents['_PAGE_EDIT_'] .= '
Edit Page
' . "\n\n"; + $contents['_PAGE_EDIT_'] .= $this->render(array('action'=>'surftoedit', 'return'=>true)); + } + $contents['HOME_LINK'] = $this->getHref($model->getInfo($model->getRootNode())); + $contents['HOME_CHILDREN'] = $this->getHomeChildren(); + $contents['BREADCRUMBS'] = $this->getBreadcrumbs(); + $contents['CHILDREN'] = $this->getChildren(); + // get ancestor + $ancestor = $this->getAncestor(); + if ($ancestor && count($ancestor)) { + $contents['ancestor'] = $ancestor['filename']; + $contents['ancestor_id'] = $ancestor[$pk]; + } + + if ($this->nterchange) { + $contents['header'] = ''; + $contents['header'] .= "\n \n "; + $contents['header'] .= ''; + $contents['header'] .= "\n "; + $contents['header'] .= ''; + } + if ($this->nterchange) { + $contents['admin_dir'] = 1; + } + if ($this->nterchange && $this->edit) { + $contents['page_edit'] = 1; + } + // set the variables so far + $this->set($contents); + // load the content into those vars using custom views + $this->set($this->getContent()); + // last-modified + $this->set('last_modified', $this->page_last_modified); + } + if (!$this->nterchange && defined('PAGE_CACHING') && PAGE_CACHING == true && $model->cache_lifetime != 0) { + $this->view_caching = true; + } + if (!$this->nterchange && defined('PAGE_CACHING') && PAGE_CACHING == true) { + foreach ($this->view_cache_lifetimes as $cache_lifetime) { + if ($this->view_cache_lifetime == -1 || $this->view_cache_lifetime > $cache_lifetime) { + $this->view_cache_lifetime = $cache_lifetime; + } + } + } + if (SITE_PRINTABLE && isset($model->printable) && $model->printable && $this->getParam('mode') == 'print') { + $this->view_caching = false; + $this->render(array('action'=>'print')); + } else { + $this->render(array('action'=>$action)); + } + } + + function loadSubnav($parameter) { + $subnav = array(); + $subnav[] = array('title'=>'Edit Page', 'action'=>'edit', 'id'=>$parameter, 'class'=>''); + $subnav[] = array('title'=>'Content', 'action'=>'content', 'id'=>$parameter, 'class'=>''); + $subnav[] = array('title'=>'Children', 'action'=>'children', 'id'=>$parameter, 'class'=>''); + foreach ($subnav as $k=>$nav) { + if ($nav['action'] == $this->action) { + $subnav[$k]['class'] = 'current'; + } + } + $this->set('subnav', $subnav); + } + + function surfToEdit($parameter) { + $this->edit = true; + $this->nterchange = true; + $this->page($parameter); + } + + function preview($parameter) { + $this->edit = false; + $this->nterchange = true; + $this->page($parameter); + } + + function do404() { + if ($redirect = &NController::factory('redirect')) { + $redirect->checkRedirect(); + } + $model = &$this->getDefaultModel(); + $model->reset(); + header("HTTP/1.1 404 Not Found"); + $this->page(4); + } + + function getHomeChildren($parent_id = null) { + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $page_info = $model->getInfo($model->$pk); + $parent_id = $parent_id?$parent_id:$model->getRootNode(); + $children = $model->getChildren($parent_id, true, true); + foreach ($children as $key=>$child) { + $children[$key]['href'] = $this->getHref($child); + } + return $children; + } + + function getBreadcrumbs($no_home=true) { + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $breadcrumbs = array_reverse($model->getAncestors($model->$pk, false, false)); + if ($no_home) { + array_shift($breadcrumbs); // shift home out of the array + } + foreach ($breadcrumbs as $key=>$breadcrumb) { + $breadcrumbs[$key]['href'] = $this->getHref($breadcrumb); + } + return $breadcrumbs; + } + + function getChildren() { + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $page_info = $model->getInfo($model->$pk); + if ($children = $model->getChildren($model->$pk, !$this->nterchange, !$this->nterchange)) { + foreach ($children as $key=>$child) { + $children[$key]['href'] = $this->getHref($child); + } + } + return $children; + } + + function getAncestor() { + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $ancestors = $model->getAncestors($model->$pk, false, false); + // pop off the home page + array_pop($ancestors); + $ancestor = count($ancestors)?array_pop($ancestors):$model->toArray(); + return $ancestor; + } + + function getAncestors(){ + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $ancestors = $model->getAncestors($model->$pk, false, false); + return $ancestors; + } + + function reorderContent($parameter) { + $this->auto_render = false; + $template_container_id = isset($this->params['template_container_id'])?(int) $this->params['template_container_id']:false; + if (!$template_container_id) return; + $this->page_title = 'Reorder Page Content'; + $model = &$this->loadModel($this->name); + $page_content = &NController::factory('page_content'); + $page_content_model = &$page_content->loadModel('page_content'); + $pk = $page_content_model->primaryKey(); + $page_content->set('page_id', $parameter); + $content_html = ''; + if ($model->get($parameter)) { + $content = array(); + $page_content_model->reset(); + $page_content_model->getContainerContent($model->{$model->primaryKey()}, $template_container_id); + $page_content->set('template_container_id', $template_container_id); + if ($page_content_model->numRows()) { + $contents = array(); + while ($page_content_model->fetch()) { + $asset_ctrl = &NController::factory($page_content_model->content_asset); + if ($asset_ctrl && $asset_model = &$asset_ctrl->loadModel($asset_ctrl->name) && $asset_model->get($page_content_model->content_asset_id)) { + $content = $asset_model->toArray(); + $content['_asset'] = $asset_ctrl->name; + $content['_asset_name'] = Inflector::humanize($asset_ctrl->name); + $content['page_content_id'] = $page_content_model->$pk; + $contents[] = $content; + } + unset($asset_ctrl); + } + $this->set('template_container_id', $template_container_id); + $this->set('contents', $contents); + $this->set(array('reorder_link'=>count($contents) > 1?true:false)); + $this->set('no_edit', true); + $content_html .= $this->render(array('action'=>'content_container', 'return'=>true)); + } + } + $content_html .= $this->render(array('action'=>'reorder_close', 'return'=>true)); + $this->set('MAIN_CONTENT', $content_html); + $this->render(array('layout'=>'simple')); + } + + function menus($parent_id = null) { + $this->nterchange = (bool) preg_match('|^/' . APP_DIR . '|', $_SERVER['REQUEST_URI']); + if ($this->nterchange && isset($this->params['edit'])) { + $this->edit = true; + } + if ($this->nterchange) { + $this->_auth = new NAuth(); + } + $model = &$this->getDefaultModel(); + $parent_id = $parent_id?(int) $parent_id:$model->getRootNode(); + $check_visible = $check_active = ($this->nterchange?false:true); + header('Content-type:text/javascript'); + + // set up the values for the view layer + $this->base_view_dir = ROOT_DIR; + // don't allow caching for "submenus" + if ($parent_id != $model->getRootNode()) { + $view_caching = false; + } else { + $view_caching = (bool) PAGE_CACHING; + } + $this->view_caching = $view_caching; + $this->view_cache_lifetime = JS_CACHE_LIFETIME; + if (!$this->nterchange && $this->view_cache_lifetime) { + header('Expires:' . gmdate('D, d M Y H:i:s \G\M\T', time() + 3600)); + header('Cache-Control:max-age=3600, must-revalidate'); + } + + // SET UP CACHING + $qualified = $this->getParam('qualify')?true:false; + if ($this->nterchange && $this->edit) { + $this->view_cache_name = 'admin_edit_javascript'; + } else if ($this->nterchange) { + $this->view_cache_name = 'admin_javascript'; + } else if (CURRENT_SITE == 'secure') { + $this->view_cache_name = 'javascript_secure'; + } else if ($qualified) { + $this->view_cache_name = 'javascript_qualified'; + } else { + $this->view_cache_name = 'javascript'; + } + // instantiate the view now that we have a view_cache_name + $view = &NView::singleton($this); + $view_options = array('action'=>'blank'); + + // check if it's currently being built, if so, wait so you can use the cached version + // this protects against multiple people building the js menus at once + $buildfile = CACHE_DIR . '/ntercache/menubuild'; + // if the menu is being built, then wait quarter second and try again. + $wait = 0; + while (1==1) { + if (!file_exists($buildfile) || $this->isCached($view_options) || time() - filemtime($buildfile) > 8) { + if ($wait > 0) $this->debug('Client waited for ' . number_format($wait) . ' microseconds for someone else to write ' . $this->view_cache_name, N_DEBUGTYPE_CACHE); + break; + } + $wait += 250000; + usleep(250000); + } + // build the menus + if (!$this->isCached($view_options)) { + @touch($buildfile); + if ($this->view_caching != false) { + $this->debug('Creating cache for ' . $this->view_cache_name . '.' , N_DEBUGTYPE_CACHE); + } + $full_url = $qualified?preg_replace('/\/$/', '', PUBLIC_SITE):''; + $subdir = $this->getParam('subdir')?$this->getParam('subdir') . '/':''; + $main_nav = $model->getChildren($parent_id, true, true); + $i = 0; + $js = ''; + $html = ''; + $preload = ''; + $img_types = array('gif', 'png', 'jpg'); + $external_cache = defined('EXTERNAL_CACHE')?constant('EXTERNAL_CACHE'):''; + foreach($main_nav as $nav) { + $width = false; + foreach ($img_types as $img_type) { + if (file_exists(DOCUMENT_ROOT . '/images/nav/' . $subdir . $nav['filename'] . '.' . $img_type)) { + if (file_exists(DOCUMENT_ROOT . '/images/nav/' . $subdir . $nav['filename'] . '_over.' . $img_type)) { + if ($preload) $preload .= ', '; + $preload .= '\'' . $external_cache . '/images/nav/' . $subdir . $nav['filename'] . '_over.' . $img_type . '\''; + } + $size = getimagesize(DOCUMENT_ROOT . '/images/nav/' . $subdir . $nav['filename'] . '.' . $img_type); + if ($size) + $width = $size[0]; + if ($model->isBranch($nav['id'], $check_active, $check_visible)) { + $html .= $this->getMenuHTML($nav['id'], $i, $i, $width); + $js .= $this->getMenuIDs($nav['id'], $i); + } + $i++; + } + } + } + $preload = "preloadImages(" . $preload . ");\n"; + $js = ereg_replace('^, ', '', $js); + $js = 'var menus = new Array(' . $js . ");\n"; + $html = "document.write('" . $html . "');\n"; + $assigns = array('CONTENT'=>$preload . $js . $html); + $this->set($assigns); + print "// non-cached file\n\n"; + @unlink($buildfile); + } else { + $this->debug('Served cached script for ' . $this->view_cache_name . '.', N_DEBUGTYPE_CACHE); + print "// cached file\n\n"; + } + $this->auto_render = false; + $this->view_caching = $view_caching; + $this->render($view_options); + } + + function getMenuHTML($id, $menuid, $cssid, $width=false, $ancestors = array()) { + $qualified = $this->getParam('qualify')?true:false; + $full_url = $qualified?preg_replace('/\/$/', '', PUBLIC_SITE):''; + $this->view_caching = false; + $model = &$this->getDefaultModel(); + $check_visible = $check_active = ($this->nterchange?false:true); + // get children of this page + $children = $model->getChildren($id, $check_active, $check_visible); + $html = ''; + $submenus = array(); + $i = 0; + if ($children) { + foreach ($children as $child) { + // set mouseover ids for the template + $mouseovers = array(); + if (is_array($ancestors)) { + foreach ($ancestors as $key=>$ancestor) { + $mouseovers[] = $ancestor['menuid']; + } + } + $mouseovers[] = $menuid; + $branch = false; + $submenu_id = false; + if ($model->isBranch($child['id'], $check_active, $check_visible)) { + $branch = true; + // add submenus for recursion + $submenus[] = array('id'=>$child['id'], 'submenuid'=>$i); + $submenu_id = $i; + $mouseovers[] = $menuid . '_' . $i; + } + if (!$child['external_url'] || (!preg_match('/^http[s]?:\/\//', $child['external_url']) && !preg_match('/^javascript:/', $child['external_url']))) { + $href = $full_url . $this->getHref($child); + } else { + $href = $this->getHref($child); + } + $this->set('full_url', $full_url); + $this->set(array('menu_id'=>$menuid, 'submenu_id'=>$submenu_id, 'page'=>$child, 'href'=>$href, 'mouseovers'=>$mouseovers, 'branch'=>$branch)); + $html .= $this->render(array('action'=>'menu_item', 'return'=>true)); + $i++; + } + } + $this->set(array('menu_id'=>$menuid, 'width'=>$width, 'js'=>$html)); + $html = $this->render(array('action'=>'menu', 'return'=>true)); + // prep the html for js inclusion + $html = str_replace(array("'", "\n", "\r"), array("\\'", '\\n', '\\n'), $html); + if (count($submenus) > 0) { + $ancestors[] = array('id'=>$id, 'menuid'=>$menuid); + foreach ($submenus as $submenu) { + $html .= $this->getMenuHTML($submenu['id'], $menuid . '_' . $submenu['submenuid'], $cssid, $width, $ancestors); + } + } + return $html; + } + + function getMenuIDs($id, $menuid) { + $model = &$this->getDefaultModel(); + $check_visible = $check_active = ($this->nterchange?false:true); + $children = $model->getChildren($id, $check_active, $check_visible); + $submenus = array(); + $i = 0; + $js = ''; + $js .= ', \'' . $menuid . '\''; + foreach ($children as $child) { + if ($model->isBranch($child['id'], $check_active, $check_visible)) { + $submenus[] = array('id'=>$child['id'], 'submenuid'=>$i); + } + $i++; + } + if (count($submenus) > 0) { + $ancestors[] = array('id'=>$id, 'menuid'=>$menuid); + foreach ($submenus as $submenu) { + $js .= $this->getMenuIDs($submenu['id'], $menuid . '_' . $submenu['submenuid'], $ancestors); + } + } + return $js; + } + + function getContent() { + // don't cache any content + $this->view_caching = false; + $page_model = &$this->getDefaultModel(); + $pk = $page_model->primaryKey(); + $content = array(); + $template_containers = $this->templateContainers($page_model->page_template_id); + $page_content = &NController::singleton('page_content'); + if (!is_array($template_containers)) return null; + $page_content->set('page_id', $page_model->$pk); + foreach ($template_containers as $container) { + if (empty($container)) continue; + if (!isset($content[$container['container_var']])) { + $content[$container['container_var']] = ''; + $content[$container['container_var'] . '_EDIT_START'] = ''; + if ($this->nterchange && $this->edit && $this->content_edit_allowed) { + $page_content->set('template_container_id', $container[$pk]); + $page_content->set('template_container_name', $container['container_name']); + $content[$container['container_var'] . '_EDIT_START'] .= $page_content->render(array('action'=>'asset_add', 'return'=>true)); + } + } + $content[$container['container_var']] .= $this->getContainerContent($page_model->id, $container['id']); + } + unset($page_content); + return $content; + } + + function getContainerContent($page_id, $container_id, $page_content_id=null) { + $page_model = &$this->getDefaultModel(); + $this->auto_render = false; + $page_id = (int)$page_id; + $container_id = (int)$container_id; + if (!$page_id || !$container_id) return null; + // instantiate the page content controller + // TODO: put some methods into the page_content controller to do some of this. + $page_content = &NController::factory('page_content'); + $page_content_model = &$page_content->getDefaultModel(); + $page_content_pk = $page_content_model->primaryKey(); + + $asset_ctrl = &NController::singleton('cms_asset_template'); + if (SITE_WORKFLOW && $this->nterchange) { + // get the users rights and bit compare them below + $workflow = &NController::factory('workflow'); + $user_rights = $workflow->getWorkflowUserRights($page_model); + } + // load up the content + $content = ''; + // set the time using a trusted source + $now = new Date(gmdate('Y-m-d H:i:s')); + $now->setTZbyID('UTC'); + if ($page_content_model->getContainerContent($page_id, $container_id, $this->nterchange, $page_content_id)) { + $page_content->set('page_id', $page_id); + while ($page_content_model->fetch()) { + $page_content->set('page_content_id', $page_content_model->$page_content_pk); + $timed_start_obj = $page_content_model->timed_start && $page_content_model->timed_start != '0000-00-00 00:00:00'?new Date($page_content_model->timed_start):false; + $timed_end_obj = $page_content_model->timed_end && $page_content_model->timed_end != '0000-00-00 00:00:00'?new Date($page_content_model->timed_end):false; + if ($timed_start_obj) { + $timed_start_obj->setTZbyID('UTC'); + } + if ($timed_end_obj) { + $timed_end_obj->setTZbyID('UTC'); + } + // set cache lifetimes for the page + if ($timed_start_obj) { + $time_diff = $timed_start_obj->getDate(DATE_FORMAT_UNIXTIME) - $now->getDate(DATE_FORMAT_UNIXTIME); + if ($time_diff > 0) { + $this->view_cache_lifetimes[] = $time_diff; + } + } + if ($timed_end_obj) { + $time_diff = $timed_end_obj->getDate(DATE_FORMAT_UNIXTIME) - $now->getDate(DATE_FORMAT_UNIXTIME); + if ($time_diff > 0) { + $this->view_cache_lifetimes[] = $time_diff; + } + } + if ($timed_end_obj && $timed_end_obj->before($now)) { + $timed_end_active = true; + } + // if the timed end is in the past then kill it and continue. + if ($timed_end_obj && $now->after($timed_end_obj)) { + // remove the content, which also kills the page cache + $page_content_controller = &NController::factory('page_content'); + $page_content_controller->_auth = &$this->_auth; + $page_content_controller->removeContent($page_content_model->$page_content_pk, false, true); + unset($page_content_controller); + continue; + } else if ($this->nterchange || !$timed_start_obj || ($timed_start_obj && $timed_start_obj->before($now))) { + $content_controller = &NController::factory($page_content_model->content_asset); + if ($content_controller && is_object($content_controller)) { + $content_model = &$content_controller->getDefaultModel(); + $fields = $content_model->fields(); + $pk = $content_model->primaryKey(); + // if we're on the public site, don't grab workflow or draft inserts + $conditions = array(); + if ($this->nterchange && in_array('cms_draft', $fields)) { + $conditions = '(cms_draft = 0 OR (cms_draft=1 AND cms_modified_by_user=' . $this->_auth->currentUserId() . '))'; + } else { + $content_model->cms_draft = 0; + } + $content_model->$pk = $page_content_model->content_asset_id; + if ($content_model->find(array('conditions'=>$conditions), true)) { + // last modified + if (strtotime($content_model->cms_modified) > $this->page_last_modified) { + $this->page_last_modified = strtotime($content_model->cms_modified); + } + $template = $asset_ctrl->getAssetTemplate($page_content_model->content_asset, $page_content_model->page_template_container_id); + if (SITE_DRAFTS && $this->nterchange) { + $is_draft = false; + $user_owned = false; + $user_id = $this->_auth->currentUserId(); + $draft_model = &NModel::factory('cms_drafts'); + $draft_model->asset = $content_controller->name; + $draft_model->asset_id = $content_model->$pk; + if ($draft_model->find(null, true)) { + $is_draft = true; + // fill the local model with the draft info + $current_user_id = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + if ($current_user_id == $draft_model->cms_modified_by_user) { + $draft_content = unserialize($draft_model->draft); + foreach ($draft_content as $field=>$val) { + $content_model->$field = $val; + } + $user_owned = true; + $draft_msg = 'You have saved'; + } else { + $user_model = &$this->loadModel('cms_auth'); + $user_model->get($draft_model->cms_modified_by_user); + $draft_msg = $user_model->real_name . ' has saved'; + unset($user_model); + } + } + unset($draft_model); + } + if (SITE_WORKFLOW && $this->nterchange) { + if ($workflow_group_model = &$workflow->getWorkflowGroup($page_model)) { + if ($current_workflow = &$workflow->getWorkflow($page_content_model->{$page_content_model->primaryKey()}, $workflow_group_model->{$workflow_group_model->primaryKey()}, $content_controller)) { + $current_user_id = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + $content_edit_allowed = $this->content_edit_allowed; + $this->content_edit_allowed = !$current_workflow->submitted && $current_user_id == $current_workflow->cms_modified_by_user?true:false; + $workflow_draft = unserialize($current_workflow->draft); + foreach ($workflow_draft as $field=>$val) { + $content_model->$field = $val; + } + } + } + } + $values = $content_model->toArray(); + $values['_EDIT_START_'] = ''; + $values['_EDIT_END_'] = ''; + if ($this->nterchange && $this->edit) { $values['_SURFTOEDIT_'] = true; } + if ($this->edit) { + if ($this->content_edit_allowed) { + // $values['_EDIT_START_'] .= '
' . "\n"; + $page_content->set(array('asset'=>$content_controller->name, 'asset_id'=>$content_model->$pk)); + $values['_EDIT_START_'] .= $page_content->render(array('action'=>'asset_edit', 'return'=>true)); + } + $page_content->set(array('asset'=>$content_controller->name, 'asset_id'=>$content_model->$pk, 'page_content_id'=>$page_content_model->$page_content_pk, 'page_id'=>$page_id)); + $values['_EDIT_START_'] .= '
' . "\n"; + if (SITE_WORKFLOW && isset($current_workflow) && $current_workflow) { + if ($this->content_edit_allowed) { + $values['_EDIT_START_'] .= '
The following content is waiting to be submitted to workflow in the dashboard.
' . "\n"; + } else { + $values['_EDIT_START_'] .= '
The following content is currently in workflow and cannot be edited.
' . "\n"; + } + } + $values['_EDIT_END_'] .= "
\n"; + if ($this->content_edit_allowed) { + if (SITE_DRAFTS && $is_draft) { + $values['_EDIT_START_'] .= '
' . $draft_msg . ' the following content as a draft.
' . "\n"; + } + $values['_EDIT_END_'] .= "
\n"; + } + } + if ($this->nterchange && (($timed_start_obj && $timed_start_obj->after($now)) || ($timed_end_obj && $timed_end_obj->after($now)))) { + $format = '%a, %b %e, %Y @ %I:%M:%S %p'; + $values['_EDIT_START_'] .= '
'; + $values['_EDIT_START_'] .= 'The following content is currently' . ($timed_start_obj && $timed_start_obj->after($now)?' NOT':'') . ' visible (it is now ' . NDate::convertTimeToClient($now, $format) . ')'; + if ($timed_start_obj && $timed_start_obj->after($now)) $values['_EDIT_START_'] .= '
It will appear: ' . NDate::convertTimeToClient($timed_start_obj, $format); + if ($timed_end_obj && $timed_end_obj->after($now)) $values['_EDIT_START_'] .= '
It will be removed: ' . NDate::convertTimeToClient($timed_end_obj, $format); + $values['_EDIT_START_'] .= '
'; + } + if (isset($content_edit_allowed)) { + $this->content_edit_allowed = $content_edit_allowed; + unset($content_edit_allowed); + } + + // Remove extra whitespace/newlines + $values['_EDIT_START_'] = trim(preg_replace('/\s+/', ' ', $values['_EDIT_START_'])); + $values['_EDIT_END_'] = trim(preg_replace('/\s+/', ' ', $values['_EDIT_END_'])); + + // Render the content + $content_controller->set($values); + $content .= $content_controller->render(array('action'=>$template, 'return'=>true)); + } + unset($content_model); + unset($content_controller); + } + } + } + } + + // free up some memory + unset($page_content_model); + unset($page_content); + // return the content + return $content; + } + + function getTemplate($template_id) { + $template_id = (int) $template_id; + $model = &$this->getDefaultModel(); + if (!$template_id) { + return false; + } + $tc_model = &NModel::factory('page_template'); + $layout = null; + if ($tc_model->get($template_id)) { + $layout = $tc_model->template_filename; + } + unset($model); + return $layout; + } + + function templateContainers($page_template_id, $id=0) { + // TODO: put some methods into the page_content controller to do some of this. + $controller_name = 'page_template_containers'; + $tc = &NController::singleton($controller_name); + $model = &$tc->getDefaultModel(); + $model->reset(); + $options = array(); + $options['conditions'] = 'page_template_id=' . (int)$page_template_id; + if ($id) { + $model->id = (int) $id; + } + $containers = array(); + if ($model->find($options)) { + while ($model->fetch()) { + $containers[] = $model->toArray(); + } + } + unset($model); + unset($tc); + return $containers; + } + + function preGenerateForm() { + $model = &$this->loadModel($this->name); + // the parent_id select field + if ($model->{$model->primaryKey()} != $model->getRootNode()) { + $model->form_elements['parent_id'] = &$this->getTreeAsSelect('parent_id', 'Parent Page'); + } else { + $model->form_ignore_fields[] = 'parent_id'; + } + } + + function postGenerateForm(&$form) { + // grab the model + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + // change the header + $el = &$form->getElement('__header__'); + if ($model->$pk) { + $el->setValue($model->title . ' - page' . $model->$pk); + } else { + $el->setValue('Create a page'); + } + // if parent_id has been passed, then use it + if ($parent_id = (int) $this->getParam('parent_id')) { + $form->setDefaults(array('parent_id'=>$parent_id)); + } + // put in some rich stuff for template changing + $page_template_value = $form->getElementValue('page_template_id'); + $page_template_value = $page_template_value[0]; + $tpl = &$form->getElement('page_template_id'); + $tpl->updateAttributes(array('onchange'=>'if(this.options[this.selectedIndex].value != ' . $page_template_value . '){Element.show(\'mv_content_check\');}else{Element.hide(\'mv_content_check\');}')); + $mv_content = &NQuickForm::createElement('checkbox', 'mv_content'); + $mv_content_html = &NQuickForm::createElement('html', ' 
' . $mv_content->toHTML() . ' Attempt to move content?
' . "\n"); + $form->insertElementBefore($mv_content_html, 'visible'); + // add section headers + if ($model->{$model->primaryKey()} == $model->getRootNode()) { + $form->removeElement('parent_id'); + } + if ($form->elementExists('permissions_id')) { + $form->insertElementBefore($form->createElement('header', null, 'Public Page Permissions'), 'permissions_id'); + } + if ($form->elementExists('external_url')) { + $form->insertElementBefore($form->createElement('header', null, 'External Link'), 'external_url'); + } + if ($form->elementExists('workflow_group_id')) { + $form->insertElementBefore($form->createElement('header', null, 'Workflow'), 'workflow_group_id'); + } + if ($form->elementExists('disclaimer_required')) { + $form->insertElementBefore($form->createElement('header', null, 'Legal Disclaimer'), 'disclaimer_required'); + } + if ($form->elementExists('meta_keywords')) { + $form->insertElementBefore($form->createElement('header', null, 'Meta Information'), 'meta_keywords'); + } + if ($form->elementExists('notify_date')) { + $form->insertElementBefore($form->createElement('header', null, 'Update Notification'), 'notify_date'); + } + parent::postGenerateForm($form); + } + + function preProcessForm(&$values) { + if (empty($values['filename'])) { + include_once 'n_filesystem.php'; + $values['filename'] = NFilesystem::cleanFileName($values['title']); + } + parent::preProcessForm($values); + } + + function postProcessForm(&$values) { + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + + // fix the paths for navigation + // if (on insert or if changed path on update) + $this->fixPaths($model->$pk); + + // move all the old content to the current template + // into matching template containers + if (isset($values['mv_content'])) { + $page_content = &NController::singleton('page_content'); + $page_content->changeTemplate($model->$pk, $model->page_template_id); + } + + // delete general caches + include_once 'n_cache.php'; + NCache::removeMenu(); + NCache::removeTreeAsSelect(); + NCache::removeJavascript($model->$pk); + + // REMOVE PAGE CACHE + $this->deletePageCache($model->$pk); + + // REMOVE PARENT PAGE CACHE (for child links, etc); + if ($this->action == 'create') { + // load a new one + $new_model = &NModel::factory($this->name); + $parent_id = $new_model->getParent($model->$pk); + unset($new_model); + } else { + // user the existing model to find the parent + $parent_id = $model->getParent($model->$pk); + } + $this->deletePageCache($parent_id); + + // remove the site admin cache + $site_admin = &NController::singleton('site_admin'); + $site_admin->deleteCache(); + unset($site_admin); + + if ($this->action == 'delete') { + $this->flash->set('notice', 'Your page has been deleted.'); + } else { + $this->flash->set('notice', 'Your page has been saved.'); + } + parent::postProcessForm($values); + } + + function getPageContent($page_id, $container_id=0, $checkactive=true) { + if (!$page_id) { + // Pear Error goes here. + } + $res = $this->getPages($page_id); + $page_info = $res->fetchRow(); + $res->free(); + $res = InputOutput::getPageTemplateContainers($page_info['page_template_id']); + $template_containers = array(); + while ($row = $this->prepareOutputContent($res->fetchRow())) { + $template_containers[] = array('id'=>$row['id'], 'var'=>$row['container_var']); + } + $page_content = array(); + foreach($template_containers as $template_container) { + if ($container_id == 0 || $container_id == $template_container['id']) { + $sql = "SELECT * FROM page p, page_content pc WHERE p.id = $page_id AND pc.page_id = p.id"; + $sql .= " AND pc.page_template_container_id = " . $template_container['id']; + $sql .= " ORDER BY"; + $sql .= " pc.page_template_container_id,"; + $sql .= " content_order, pc.id"; + $res = $this->db->query($sql); + while ($row = $this->prepareOutputContent($res->fetchRow())) { + if ($row['timed_end'] != '0000-00-00 00:00:00' && strtotime($row['timed_end']) < time()) { + $this->deletePageContent($row['id']); + } else { + $io = InputOutput::singleton($row['content_object']); + $cres = $io->getRecords($row['content_object_id'], $checkactive); + if (!DB::isError($cres)) { + $crow = $this->prepareOutputContent($cres->fetchRow()); + if ($crow) $page_content[$template_container['var']][] = array('id'=>$crow['id'], 'page_template_container_id'=>$row['page_template_container_id'], 'object'=>$io->getObject(), 'object_name'=>$io->getObjectName(), 'headline'=>$crow['cms_headline'], 'page_content_id'=>$row['id'], 'timed_start'=>$row['timed_start'], 'timed_end'=>$row['timed_end']); + } + } + } + } + } + + return $page_content; + } + + function changeContentOrder($page_id, $content) { + foreach($content as $key=>$id) { + $ordernum = $key + 1; + $sql = 'UPDATE page_content SET content_order=' . $ordernum . ' WHERE page_id=' . $page_id . ' AND id=' . $id; + $this->db->query($sql); + } + $this->deletePageCache($page_id); + return true; + } + + function getLink($page_info, $treat_title=false) { + $model = $this->getDefaultModel(); + $link = ''; + $title = $page_info['title']; + if (is_callable($treat_title)) { + $title = call_user_func($treat_title, $title); + } + if ($page_info['active'] == 0) { + $title = '[' . $title . ']'; + } else if ($page_info['visible'] == 0) { + $title = '(' . $title . ')'; + } + $link .= $title; + $link .= ''; + return $link; + } + function getHref($page_info, $branch=null) { + $model = &$this->getDefaultModel(); + if (defined('IN_NTERCHANGE') && IN_NTERCHANGE && defined('IN_SURFTOEDIT') && IN_SURFTOEDIT) { + $href = '/' . APP_DIR . '/page/surftoedit/' . $page_info[$model->primaryKey()]; + } else if (defined('IN_NTERCHANGE') && IN_NTERCHANGE) { + $href = '/' . APP_DIR . '/page/preview/' . $page_info[$model->primaryKey()]; + } else { + if ($page_info['external_url']) { + $href = $page_info['external_url']; + } else { + $href = $page_info['path']; + if ($branch === null) { + if ($model->isBranch($page_info[$model->primaryKey()], true, true)) { + $href .= '/'; + } else { + $href .= '.' . DEFAULT_PAGE_EXTENSION; + } + } else if ($branch == false) { + $href .= '.' . DEFAULT_PAGE_EXTENSION; + } else { + $href .= '/'; + } + $href = ((defined('SECURE_SITE') && SECURE_SITE != false && $page_info['secure_page'] != 0)?preg_replace('|/$|', '', SECURE_SITE):((CURRENT_SITE == 'secure')?preg_replace('|/$|', '', PUBLIC_SITE):'')) . $href; + } + } + return $href; + } + function getTarget($page_info) { + if ($this->nterchange && $page_info['external_url'] != '' && $page_info['external_url_popout'] != 0) { + return ' target="_blank"'; + } + return ''; + } + + function deletePageCache($id) { + if (!empty($id)) { + // load the model + $model = &NModel::singleton($this->name); + $model->reset(); + if ($model->get($id)) { + $pk = $model->primaryKey(); + // find the action + $action = $this->getTemplate($model->page_template_id); + $action = $action?$action:'default'; + $action = $this->getTemplate($model->page_template_id); + $action = $action?$action:'default'; + // set up caching values + $this->base_view_dir = ROOT_DIR; + $this->view_caching = true; + $this->view_cache_lifetime = $model->cache_lifetime; + $this->view_cache_name = 'page' . $id; + $view = &NView::singleton($this); + if ($this->isCached($action)) { + $cleared = $view->clearCache($action); + $this->debug('Page cache for Page ID ' . $id . ($cleared?' removed':' failed attempted removal') . '.', N_DEBUGTYPE_CACHE); + } else { + $this->debug('Page cache for Page ID ' . $id . ' failed attempted removal since cache does not exist.', N_DEBUGTYPE_CACHE); + } + // Check the smarty_cache folder for additional caches from query string pages. + $query_string_caches = CACHE_DIR . '/smarty_cache/page' . $id . '%*'; + if (count(glob($query_string_caches)) != 0) { + $files = glob($query_string_caches); + foreach ($files as $file) { + unlink($file); + } + NDebug::debug('Deleted query string cache files for page id ' . $id , N_DEBUGTYPE_INFO); + } + } + unset($model); + unset($view); + } + return; + } + + function fixPaths($id=0) { + $model = &NModel::singleton($this->name); + $model->reset(); + if (!$id) $id = $model->getRootNode(); + $fields = $model->fields(); + if ($model->get($id)) { + // update the current page + $model->path = $model->buildPath($id); + if (in_array('cms_modified_by_user', $fields)) { + $model->cms_modified_by_user = isset($this->_auth)?(int) $this->_auth->currentUserID():0; + } + $model->update(); + // update all the children + $all_children = $model->getAllChildren($id, false, false); + foreach ($all_children as $child) { + $model->reset(); + $model->get($child[$model->primaryKey()]); + $model->path = $model->buildPath($child['id']); + if (in_array('cms_modified_by_user', $fields)) { + $model->cms_modified_by_user = isset($this->_auth)?(int) $this->_auth->currentUserID():0; + } + $model->update(); + } + } + unset($model); + } + + function &getTreeAsSelect($name, $label) { + include_once 'n_cache.php'; + include_once 'n_quickform.php'; + if (!$options = NCache::getTreeAsSelect()) { + $options = $this->getOptions(false); + if ($options) { + NCache::createTreeAsSelect($options); + } + } + return NQuickForm::createElement('select', $name, $label, $options); + } + + function getOptions($options=false, $id=0, $level=0) { + $spaces = str_repeat('- ', $level); + $model = &$this->getDefaultModel(); + $children = $model->getChildren($id, !$this->nterchange, !$this->nterchange); + if (!$options) $options = array(); + if (is_array($children)) { + for ($x=0;$xgetOptions($options, $children[$x]['id'], $level+1); + } + return $options; + } + } + + function evalHTML($tpl_output) { + $pos = 0; + // Loop through to find the php code in html... + while (($pos = strpos($tpl_output, '', $pos + 5); + // Eval outputs directly to the buffer. Catch / Clean it + ob_start(); + eval(substr($tpl_output, $pos + 5, $pos2 - $pos - 5)); + $value = ob_get_contents(); + ob_end_clean(); + // Grab that chunk! + $tpl_output = substr($tpl_output, 0, $pos) . $value . substr($tpl_output, $pos2 + 2); + } + return $tpl_output; + } + + function render($options=array()) { + $return = isset($options['return'])?$options['return']:false; + $options['return'] = true; + $page = parent::render($options); + $page = $this->evalHTML($page); + if ($return) return $page; + print $page; + } + + function sitemap($params=array()) { + $model = &$this->getDefaultModel(); + $page_id = isset($params['page_id'])?$params['page_id']:$model->getRootNode(); + $treat_title = isset($params['treat_title'])?$params['treat_title']:null; + $title_recurse = isset($params['treat_title_recurse'])?(bool) $params['treat_title']:false; + if ($this->nterchange) { + $checkactive = false; + $checkvisible = false; + } else { + $checkactive = true; + $checkvisible = true; + } + $main_nav = $model->getChildren($page_id, $checkactive, $checkvisible); + $html = '
'; + + foreach($main_nav as $nav) { + $html .= "
  • "; + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $nav['id']) $html .= ''; + $html .= $this->getLink($nav, $treat_title); + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $nav['id']) $html .= ''; + + if ($model->isBranch($nav['id'], $checkactive, $checkactive)) { + $sub_treat_title = ($title_recurse == true)?$treat_title:false; + $html .= $this->getChildList($nav['id'], $sub_treat_title); + } + $html .= "
\n"; + } + $html .="
"; + return $html; + } + + function getChildList($page_id, $treat_title=false) { + $model = &$this->getDefaultModel(); + if ($this->nterchange) { + $checkactive = false; + $checkvisible = false; + } else { + $checkactive = true; + $checkvisible = true; + } + $children = $model->getChildren($page_id, $checkactive, $checkvisible); + $html = ''; + $submenu = array(); + foreach ($children as $child) { + $html .= "\t
  • "; + $info = $model->getInfo($child['id']); + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $info['id']) $html .= ''; + $html .= $this->getLink($info, $treat_title); + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $info['id']) $html .= ''; + if ($model->isBranch($child['id'], $checkactive, $checkactive)) { + $html .= $this->getChildList($child['id'], $treat_title); + } + $html .= "
  • \n"; + } + if ($html) { + $html = "
      \n" . $html . "
    \n\n"; + } + return $html; + } +} diff --git a/app/controllers/page_template_containers_controller.php b/app/controllers/page_template_containers_controller.php new file mode 100644 index 0000000..c08a8d1 --- /dev/null +++ b/app/controllers/page_template_containers_controller.php @@ -0,0 +1,106 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageTemplateContainersController extends AdminController { + function __construct() { + $this->name = 'page_template_containers'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + $this->login_required = true; + parent::__construct(); + } + + function viewlist($page_template_id) { + $this->loadSubnav($page_template_id); + $this->auto_render = false; + $html = ''; + if (!$page_template_id) { + // This is a bit of a hack. + header ('Location: /nterchange/page_template/viewlist'); + } + $model = $this->getDefaultModel($this->name); + $model->page_template_id = $page_template_id; + // Let's get more information about the page_template. + if ($page_template = $model->getLink('page_template_id', 'page_template')) { + $this->set('page_template_name', $page_template->template_name); + $this->set('page_template_filename', $page_template->template_filename); + } + $this->set('page_template_id', $page_template_id); + if ($model->find()) { + while ($model->fetch()) { + $arr = $model->toArray(); + $arr['_headline'] = isset($arr['cms_headline']) && $arr['cms_headline']?$arr['cms_headline']:$model->makeHeadline(); + $models[] = $arr; + unset($arr); + } + $html .= $this->set('rows', $models); + } else { + $this->set('notice', 'There are no containers for that template.'); + } + $html .= $this->set(array('asset'=>$this->name, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + $html .= $this->render(array('layout'=>'default')); + return $html; + } + + function create($parameter=null, $layout=true) { + $this->page_template_id = $parameter; + $this->loadSubnav($parameter); + parent::create($parameter); + } + + function edit($parameter) { + $this->page_template_id = $this->getPTIFromId($parameter); + $this->set('page_template_id', $this->page_template_id); + $this->loadSubnav($parameter); + parent::edit($parameter); + } + + // Get page_template_id from page_template_container_id. + function getPTIFromId($id) { + $model = &NModel::factory($this->name); + $model->id = $id; + if($model->find()) { + while ($model->fetch()) { + $result = $model->toArray(); + } + $page_template_id = $result['page_template_id']; + } + unset($model); + return $page_template_id; + } + + function postGenerateForm(&$form) { + $form->removeElement('__header__'); + $form->addRule('container_var', 'We need to have a variable for this container.', 'required', null, 'client'); + $form->addRule('container_name', 'We need to have a name for this container.', 'required', null, 'client'); + $form->addRule('container_var', 'Uppercase letters, numbers and underscores - without spaces or punctuation.', 'regex', '/^[A-Z0-9_]+$/', 'client'); + // Set the page_template in the menu as passed by $parameter. + $template_group = &$form->getElement('page_template_id'); + $template_group->setSelected($this->page_template_id); + // Not sure I should do this - but it seems to help with confusion. + $template_group->freeze(); + } + + function &getDefaultModel() { + $model = &$this->loadModel('page_template_containers'); + return $model; + } + +} +?> \ No newline at end of file diff --git a/app/controllers/page_template_controller.php b/app/controllers/page_template_controller.php new file mode 100644 index 0000000..e80ab31 --- /dev/null +++ b/app/controllers/page_template_controller.php @@ -0,0 +1,52 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageTemplateController extends AdminController { + function __construct() { + $this->name = 'page_template'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + $this->login_required = true; + $this->page_title = 'Templates'; + parent::__construct(); + } + + function &getDefaultModel() { + $model = &$this->loadModel('page_template'); + return $model; + } + + function doesPageTemplateExist($filename){ + $full_path_filename = ASSET_DIR . '/views/page/' . $filename . '.' . DEFAULT_PAGE_EXTENSION; + if (file_exists($full_path_filename)) { + return true; + } else { + return false; + } + } + + function postGenerateForm(&$form) { + $form->removeElement('__header__'); + $form->addRule('template_filename', 'We need to have a filename.', 'required', null, 'client'); + $form->addRule('template_name', 'We need to have a name for this template.', 'required', null, 'client'); + $form->addRule('template_filename', 'Letters, numbers, dashes and underscores - without a suffix, spaces or punctuation.', 'regex', '/^[a-zA-Z0-9_-]+$/', 'client'); + //'Code contains numbers only', 'regex', '/^\d+$/' + } +} +?> \ No newline at end of file diff --git a/app/controllers/phpinfo_controller.php b/app/controllers/phpinfo_controller.php new file mode 100755 index 0000000..202c8b6 --- /dev/null +++ b/app/controllers/phpinfo_controller.php @@ -0,0 +1,31 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PhpinfoController extends AppController { + function __construct() { + $this->login_required = true; + parent::__construct(); + } + + function index() { + $this->auto_render = false; + phpinfo(INFO_ALL ^ INFO_ENVIRONMENT); + } +} +?> diff --git a/app/controllers/redirect_controller.php b/app/controllers/redirect_controller.php new file mode 100644 index 0000000..e2fceec --- /dev/null +++ b/app/controllers/redirect_controller.php @@ -0,0 +1,94 @@ + + * @author Darron Froese + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class RedirectController extends AssetController { + function __construct() { + $this->name = 'redirect'; + $this->versioning = true; + $this->base_view_dir = ROOT_DIR; + $this->redirect_delay = 3; + parent::__construct(); + } + + /** + * checkRedirect - Look in the database to see if there's a redirect to follow. + * If there is - hit renderRedirect. + * + * @return void + **/ + function checkRedirect() { + include_once 'n_server.php'; + $current_url = NServer::env('REQUEST_URI'); + // Check DB for direct match. + $model = &$this->getDefaultModel(); + $model->reset(); + $model->url = $current_url; + $model->regex = 0; + $model->find(); + while ($model->fetch()) { + $this->renderRedirect($model->toArray()); + } + // Let's look at the regex matches. + $model->reset(); + $model->regex = 1; + $model->find(); + $urls = &$model->fetchAll(); + foreach ($urls as $url) { + if ($url->regex != 0 && eregi($url->url, $current_url)) { + $this->renderRedirect($url->toArray()); + } + } + } + + /** + * renderRedirect - Render a page that automatically redirects to the required location. + * Doing it this way allows your regular website statistics to generate reports. + * If there isn't a template to use, then just redirect and count the old way. + * + * @param array A redirect model object converted toArray(); + * @return void + **/ + function renderRedirect($array) { + if (file_exists(ASSET_DIR . '/views/redirect/default.html')) { + // Do the new style page load and render. + $contents['_SITE_NAME_'] = htmlentities(SITE_NAME); + $contents['_EXTERNAL_CACHE_'] = defined('EXTERNAL_CACHE') && constant('EXTERNAL_CACHE')?EXTERNAL_CACHE:false; + $this->set('title', 'Redirecting...'); + $this->set('redirect_delay', $this->redirect_delay); + $this->set('header', ''); + $this->set($contents); + $this->set($array); + $this->render(array('action'=>'default')); + die; + } else { + // Do the old redirect style and count. + // $url = &$this->getDefaultModel(); + // $url->id = $array['id']; + // $url->get(); + // $url->count += 1; + // $url->update(); + if ($address = $array['redirect']) { + header ("Location: $address"); + die; + } + } + } +} +?> \ No newline at end of file diff --git a/app/controllers/rss_controller.php b/app/controllers/rss_controller.php new file mode 100644 index 0000000..5ed7e41 --- /dev/null +++ b/app/controllers/rss_controller.php @@ -0,0 +1,116 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since Version 3.1.17 + */ +class RSSController extends AssetController { + function __construct() { + $this->name = 'rss'; + $this->page_title = 'RSS Feeds'; + $this->records = 50; + $this->set('_SITE_NAME_', SITE_NAME); + parent::__construct(); + } + + /** + * generateFeedToken - Generate a feed_token for a logged in user. + * Puts it into the database and returns to the passed url. + * + * @return void + **/ + function generateFeedToken() { + $redirect_url = isset($_GET['redirect'])?$_GET['redirect']:'/nterchange'; + $random = $_SERVER['REMOTE_ADDR'] . rand(0,1000000) . time(); + $tmp_feed_token = md5($random); + $auth = new NAuth(); + $user_id = $auth->currentUserID(); + unset($auth); + $cms_user = NModel::factory('cms_auth'); + $cms_user->id = $user_id; + if ($cms_user->find()) { + while ($cms_user->fetch()) { + $cms_user->feed_token = $tmp_feed_token; + $cms_user->save(); + header("Location:$redirect_url"); + } + } + } + + /** + * checkToken - Check whether or not the token is present in the DB. + * + * @param string The offered token. + * @return boolean + **/ + function checkToken($token) { + // Check the token to the ones in the database. + $cms_auth = NModel::factory('cms_auth'); + $cms_auth->feed_token = $token; + if ($cms_auth->find()) { + return true; + } else { + return false; + } + } + + /** + * getToken - Get a token as passed on the $_GET string. + * + * @return string The $_GET['token'] passed + **/ + function getToken() { + return $_GET['token']; + } + + /** + * auditTrail - Create an RSS feed of audit trail records. + * Shows $this->records many records. + * + * @return void + **/ + function auditTrail() { + if (defined('RSS_AUDIT_TRAIL') && RSS_AUDIT_TRAIL) { + $this->auto_render = false; + $count = 0; + $token = $this->getToken(); + // It's got to be 32 characters - this keeps people from trying token= + if ($length = strlen($token) < 32) die; + if ($allowed = $this->checkToken($token)) { + // Grab the last 50 results + $audit_trail = NModel::factory('cms_audit_trail'); + $options['order_by'] = 'cms_created DESC'; + if ($audit_trail->find($options)) { + while ($audit_trail->fetch()) { + $audit_trail_controller = NController::factory('audit_trail'); + $record = $audit_trail_controller->humanizeAuditTrailRecord($audit_trail); + //varDump($record); + $records[] = $record; + $count++; + if ($count >= $this->records) break; + } + } + $this->set('records', $records); + $this->render(array('action'=>'audit_trail')); + } else { + print "Unauthorized access"; + } + } + } + +} +?> diff --git a/app/controllers/search_controller.php b/app/controllers/search_controller.php new file mode 100644 index 0000000..ddd866e --- /dev/null +++ b/app/controllers/search_controller.php @@ -0,0 +1,145 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class SearchController extends AppController { + function __construct() { + $this->name = 'search'; + $this->versioning = true; + $this->base_view_dir = ROOT_DIR; + parent::__construct(); + } + + function search() { + include_once 'HTTP/Header.php'; + $header = new HTTP_Header; + $search = htmlentities($this->getParam('search')); + $html = ''; + $this->set('request_uri', NServer::env('PHP_SELF')); + $this->set('search', $search); + $html .= $this->render(array('action'=>'search_form', 'return'=>true)); + if ($search && function_exists('udm_alloc_agent')) { + $current_page = $this->getParam('page')?(int) $this->getParam('page'):0; + + // instantiate the search agent + $udm_agent = udm_alloc_agent(DB_DSN_SEARCH); + $page_size = 10; + + // pull in the stopwords and remove them from the query + $stopfile = defined('MNOGO_STOPFILE')?constant('MNOGO_STOPFILE'):'/usr/local/mnogosearch/etc/stopwords/en.huge.sl'; + $tmp = file($stopfile); + $stopwords = array(); + for($i=0;$i 0?$total_rows-1:$total_rows; + $page_rows = udm_get_res_param($res, UDM_PARAM_NUM_ROWS); + $first_doc = udm_get_res_param($res, UDM_PARAM_FIRST_DOC); + $last_doc = udm_get_res_param($res, UDM_PARAM_LAST_DOC); + $total_pages = ceil($total_rows/$page_size); + + // set general template values + $this->set('rows', $total_rows); + $template_pages = array(); + for ($i=0;$i<$total_pages;$i++) { + $template_pages[] = $i+1; + } + $search_url = NServer::env('PHP_SELF') . '?search=' . $search . '&page='; + $this->set('stopped_words', $stopped_words); + $this->set('search_url', $search_url); + $this->set('current_page', $current_page); + $this->set('pages', $template_pages); + $this->set('previous_page', ($current_page>0)?$current_page-1:-1); + $this->set('next_page', ($current_page+1<$total_pages)?$current_page+1:-1); + + // gather the results and pass them to the template + $items = array(); + for ($i=0;$i<$page_rows;$i++) { + $item['title'] = udm_get_res_field($res, $i, UDM_FIELD_TITLE); + $item['url'] = udm_get_res_field($res, $i, UDM_FIELD_URL); + $item['text'] = udm_get_res_field($res, $i, UDM_FIELD_TEXT); + $item['size'] = udm_get_res_field($res, $i, UDM_FIELD_SIZE); + $item['filesize'] = udm_get_res_field($res, $i, UDM_FIELD_SIZE); + if ($item['filesize']) $item['filesize'] = NFilesystem::filesize_format($item['filesize']); + $item['rating'] = udm_get_res_field($res, $i, UDM_FIELD_RATING); + $item['title'] = $item['title']?$this->cleanupItem(htmlspecialchars($item['title'])):basename($item['url']); + $item['text'] = $this->cleanupItem($item['text']); + $items[] = $item; + } + $this->set('items', $items); + $html .= $this->render(array('action'=>'found_items', 'return'=>true)); + udm_free_res($res); + udm_free_agent($udm_agent); + } else { + $html .= '

    You have not entered any search queries - please enter one in the form above.

    '; + } + return $html; + } + + function search404() { + if ($this->getParam('search')) { + return $this->search(); + } + $uri = NServer::env('PHP_SELF'); + $words = explode('/', $uri); + // remove any empty elements + foreach ($words as $i=>$val) { + if(empty($val)){ + unset($words[$i]); + } + } + $this->setParam('search', urldecode(implode(' ', $words))); + return $this->search(); + } + + function cleanupItem($var) { + include_once 'model/value_cast.php'; + $var = ValueCast::toLatinISO($var); + // some weird mnogosearch characters to replace + $var = preg_replace('/\\x02/', '', $var); + $var = preg_replace('/\\x03/', '', $var); + return $var; + } +} +?> diff --git a/app/controllers/settings_controller.php b/app/controllers/settings_controller.php new file mode 100644 index 0000000..a304c80 --- /dev/null +++ b/app/controllers/settings_controller.php @@ -0,0 +1,109 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class SettingsController extends nterchangeController { + function __construct() { + $this->name = 'settings'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + // $this->public_actions = array(); + $this->login_required = true; + parent::__construct(); + } + + function &getDefaultModel() { + return $this->loadModel('cms_settings'); + } + + function index() { + $this->redirectTo('viewlist'); + $this->auto_render = false; + $model = &$this->getDefaultModel(); + + $this->render(array('layout'=>'default')); + } + + function viewlist() { + $this->auto_render = false; + include_once 'n_quickform.php'; + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $setting_forms = array(); + $user_settings = $GLOBALS['USER_SETTINGS']; + foreach ($user_settings as $setting=>$default) { + $model->reset(); + $model->user_id = (int) $this->_auth->currentUserId(); + $model->setting = $setting; + $form = new NQuickForm('setting_' . $setting); + $form->addElement('header', null, $model->settingToText($setting)); + $description = $this->getSettingDescription($setting); + if (!$description) { + $description = 'Setting'; + } + $form->addElement('hidden', 'setting', $setting); + $checkbox = &$form->addElement('checkbox', 'value', $description, null, array('id'=>'qf_' . $model->setting)); + if ($model->find(null, true)) { + // set the form action to edit + $form->updateAttributes(array('action'=>'/' . APP_DIR . '/' . $this->name . '/edit/' . $model->$pk)); + $form->addElement('hidden', $pk, $model->$pk); + // check the box according to the value + $checkbox->setChecked((bool) $model->value); + } else { + $form->updateAttributes(array('action'=>'/' . APP_DIR . '/' . $this->name . '/create')); + $checkbox->setChecked((bool) $default); + } + $form->addElement('hidden', '_referer', urlencode(NServer::env('REQUEST_URI'))); + $form->addElement('submit', '__submit__', 'Submit'); + $form->addRule('setting', null, 'required'); + $setting_forms[] = &$form; + } + $this->set('settings', $setting_forms); + $this->render(array('layout'=>'default')); + } + + function show($parameter) { + $model = &$this->getDefaultModel(); + $model->user_id = (int) $this->_auth->currentUserId(); + return parent::show($parameter); + } + + function edit($parameter) { + $model = &$this->getDefaultModel(); + $model->user_id = (int) $this->_auth->currentUserId(); + $this->flash->set('notice', 'Your preference has been saved.'); + return parent::edit($parameter); + } + + function create($parameter) { + $model = &$this->getDefaultModel(); + $model->user_id = (int) $this->_auth->currentUserId(); + $this->flash->set('notice', 'Your preference has been saved.'); + return parent::create($parameter); + } + + function getSettingDescription($setting) { + switch ($setting) { + case SETTINGS_EDITOR: + $ret = 'WYSIWYG Editor
    Choose whether you want the editor to be active for you or not.'; + break; + } + return $ret; + } +} +?> diff --git a/app/controllers/site_admin_controller.php b/app/controllers/site_admin_controller.php new file mode 100644 index 0000000..9d36b9b --- /dev/null +++ b/app/controllers/site_admin_controller.php @@ -0,0 +1,201 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class SiteAdminController extends nterchangeController { + function __construct() { + if (get_class($this) == __CLASS__) { + $this->name = 'site_admin'; + // login required + $this->login_required = true; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + // set up view caching values + $this->view_cache_name = 'nterchange_site_admin'; + $this->view_caching = true; + $this->view_cache_lifetime = -1; // forever + } + parent::__construct(); + } + + function index() { + $this->nterchange = true; + $this->auto_render = false; + // preset layout values + $main_content = null; + $sidebar = null; + $this->view_cache_name = 'nterchange_site_admin_' . $this->_auth->currentUserID(); + // check if it's currently being built, if so, wait so you can use the cached version + // this protects against multiple people building the site admin at once + $buildfile = CACHE_DIR . '/ntercache/siteadminbuild'; + // if the menu is being built, then wait quarter second and try again. + $wait = 0; + while (1==1) { + if (!file_exists($buildfile) || $this->isCachedLayout('default') || time() - filemtime($buildfile) > 8) { + if ($wait > 0 && defined('LOG_CACHE') && LOG_CACHE == true) $this->debug('Client waited for ' . number_format($wait) . ' microseconds for someone else to write ' . $this->view_cache_name); + break; + } + $wait += 250000; + usleep(250000); + } + if (!$this->isCachedLayout('default')) { + @touch($buildfile); + $this->set('user_level', $this->_auth->getAuthData('user_level')); + $this->page_title = 'Site Admin'; + $this->set(array('asset'=>$this->name, 'sitemap_list'=>$this->siteAdminList())); + $main_content = $this->render(array('return'=>true, 'action'=>'site_admin')); + @unlink($buildfile); + } + // set caching back to true before rendering as it was set to false in siteAdminList() + $this->view_caching = true; + $this->renderLayout('default', $main_content, $sidebar); + } + + function siteAdminList($id=null) { + // set view caching to false so as to not cache every item + $this->view_caching = false; + $page_ctrl = &NController::singleton('page'); + $model = &$page_ctrl->getDefaultModel(); + $model->reset(); + $pk = $model->primaryKey(); + $html = ''; + $model->parent_id = $id?(int) $id:'null'; + if ($model->find()) { + $this->set('reorder', ($id==0?false:true)); + $this->set('parent_id', $id); + $html .= $this->render(array('action'=>'site_admin_list_start', 'return'=>true)); + $i = 0; + $assigns['_referer'] = urlencode(NServer::env('REQUEST_URI')); + $pages = &$model->fetchAll(); + foreach ($pages as $page) { + $page_edit = false; + $surfedit = false; + switch ($this->_auth->getAuthData('user_level')) { + case N_USER_EDITOR: + $surfedit = true; + break; + case N_USER_ADMIN: + case N_USER_ROOT: + $page_edit = true; + $surfedit = true; + break; + } + if (SITE_WORKFLOW) { + $assigns['workflow'] = ''; + $workflow = &NController::singleton('workflow'); + if ($workflow_group_model = &$workflow->getWorkflowGroup($page)) { + $user_rights = $workflow->getWorkflowUserRights($page); + if ($user_rights & WORKFLOW_RIGHT_EDIT) { + $surfedit = true; + } + $assigns['workflow'] = $workflow_group_model->workflow_title; + } + } + $assigns['id'] = $page->$pk; + $assigns['title'] = $page->title; + $assigns['active'] = $page->active; + $assigns['visible'] = $page->visible; + $assigns['page_edit'] = $page_edit; + $assigns['surfedit'] = $surfedit; + $assigns['odd_or_even'] = $i%2 == 0?'even':'odd'; + $this->set($assigns); + $html .= $this->render(array('action'=>'sitemap_list_item', 'return'=>true)); + $i++; + $html .= $this->siteAdminList($page->$pk); + } + unset($pages); + $html .= $this->render(array('action'=>'site_admin_list_end', 'return'=>true)); + } + unset($model, $page_ctrl); + return $html; + } + + /** + * clearAllCache + * + * front-end method for clearing caches (see clearCache for the actual work) + */ + function clearAllCache() { + $this->page_title = 'Clear all caches'; + $this->auto_render = false; + $this->clearCache(); + $this->render(array('layout'=>'default')); + } + + /** + * clearCache + * + * clears the view and database caches as well as CDN cache if applicable + */ + function clearCache() { + $this->view_cache_name = 'clear_all_cache'; + $this->view_caching = false; + $view = &NView::singleton($this); + $view->clear_all_cache(); + // Remove cache folder contents + $this->rmDirFiles(CACHE_DIR . '/templates_c'); + $this->rmDirFiles(CACHE_DIR . '/smarty_cache'); + $this->rmDirFiles(CACHE_DIR . '/ntercache'); + $this->rmDirFiles(CACHE_DIR . '/ntercache/db'); + $this->rmDirFiles(CACHE_DIR . '/ntercache/code_caller'); + $this->rmDirFiles(CACHE_DIR . '/magpie'); + + // Clear CDN cache if needed. + if (defined('CDN_CLEAR') && CDN_CLEAR) { + if (defined('CDN_TYPE')) { + $this->debug('Clearing the CDN cache'); + $cdn_file = realpath(ROOT_DIR . '/lib/cdn/' . CDN_TYPE . '.php'); + if (file_exists($cdn_file)) { + $this->debug('CDN ' . CDN_TYPE . ' exists.'); + include($cdn_file); + } + } + } + } + + function rmDirFiles($dir) { + if (!$dir) return; + $dir = preg_replace('/\/$/', '', $dir); + if (!file_exists($dir) || !is_dir($dir)) return; + if (!$dh = @opendir($dir)) return; + while (false !== ($file = readdir($dh))) { + if (preg_match('/^\./', $file)) { + continue; + } + @unlink($dir . '/' . $file); + } + closedir($dh); + return true; + } + + function deleteCache() { + $auth_model = &NModel::factory('cms_auth'); + if ($auth_model->find()) { + $pk = $auth_model->primaryKey(); + while ($auth_model->fetch()) { + // remove the site admin cache + $this->view_cache_name = 'nterchange_site_admin_' . $auth_model->$pk; + $view = &NView::singleton($this); + $view->clearLayoutCache('default'); + } + } + unset($auth_model); + unset($view); + } +} +?> diff --git a/app/controllers/test_sample_controller.php b/app/controllers/test_sample_controller.php new file mode 100644 index 0000000..565a0cf --- /dev/null +++ b/app/controllers/test_sample_controller.php @@ -0,0 +1,29 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class TestSampleController extends AppController { + function __construct() { + $this->name = 'test_sample'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + $this->login_required = true; + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/controllers/users_controller.php b/app/controllers/users_controller.php new file mode 100644 index 0000000..ee5a14f --- /dev/null +++ b/app/controllers/users_controller.php @@ -0,0 +1,207 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class UsersController extends AdminController { + function __construct() { + $this->name = 'users'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + $this->login_required = true; + $this->page_title = 'Users'; + parent::__construct(); + } + + function index($parameter) { + $current_user_level = $this->_auth->getAuthData('user_level'); + if ($current_user_level < N_USER_ADMIN) { + $user_id = $this->_auth->getAuthData('id'); + $this->redirectTo('edit', $user_id); + } + $this->redirectTo('viewlist', $parameter); + } + + function viewlist($parameter) { + $current_user_level = $this->_auth->getAuthData('user_level'); + if ($current_user_level < N_USER_ADMIN) { + $this->redirectTo('index', $parameter); + } + parent::viewlist($parameter); + } + + function show($parameter) { + $this->checkUserId($parameter); + parent::show($parameter); + } + + function edit($parameter) { + $this->checkUserId($parameter); + parent::edit($parameter); + } + + function delete($parameter) { + $current_user_level = $this->_auth->getAuthData('user_level'); + if ($current_user_level < N_USER_ADMIN) { + $this->redirectTo('index', $parameter); + } + parent::delete($parameter); + } + + function checkUserId($parameter) { + $current_user_level = $this->_auth->getAuthData('user_level'); + $current_user_id = $this->_auth->getAuthData('id'); + if ($current_user_level < N_USER_ADMIN && $current_user_id != $parameter) { + $this->redirectTo('index', $parameter); + } + return true; + } + + function &getDefaultModel() { + $model = &$this->loadModel('cms_auth'); + return $model; + } + + function preGenerateForm() { + // user level - a non-root can't set someone else to be root + $model = &$this->getDefaultModel(); + $current_user_level = $this->_auth->getAuthData('user_level'); + $user_lvl = &$model->form_elements['user_level']; + if ($current_user_level >= N_USER_ROOT) { + $user_lvl[3][N_USER_ROOT] = 'root'; + } + if (!$model->{$model->primaryKey()}) { + $model->form_required_fields[] = 'password'; + } + parent::preGenerateForm(); + } + + function postGenerateForm(&$form) { + $model = &$this->getDefaultModel(); + $current_user_level = $this->_auth->getAuthData('user_level'); + // empty the password field manually + $password = &$form->getElement('password'); + $password->setValue(''); + // turn status on by default + // $status = &$form->getElement('status'); + // $status->setChecked(true); + // put in confirmation password field + $form->insertElementBefore(NQuickForm::createElement('password', 'confirm_password', 'Confirm Password'), 'user_level'); + if ($model->{$model->primaryKey()}) { + $password->setLabel('Current Password'); + $form->insertElementBefore(NQuickForm::createElement('password', 'new_password', 'New Password'), 'confirm_password'); + $form->addRule(array('new_password', 'confirm_password'), 'The passwords do not match', 'compare'); + $form->addRule('new_password', 'The new password must be at least 8 characters long and contain upper and lower case characters and a number.', 'minlength', 8, 'client'); + $form->addRule('new_password', 'The new password must be at least 8 characters long and contain upper and lower case characters and a number.', 'regex', '/[A-Z]/', 'client'); + $form->addRule('new_password', 'The new password must be at least 8 characters long and contain upper and lower case characters and a number.', 'regex', '/[a-z]/', 'client'); + $form->addRule('new_password', 'The new password must be at least 8 characters long and contain upper and lower case characters and a number.', 'regex', '/[0-9]/', 'client'); + $password = &$form->removeElement('password'); + if ($current_user_level < N_USER_ADMIN) { + $form->insertElementBefore($password, 'new_password'); + $password->setValue(''); + $form->addFormRule(array(&$this, 'validateEdit')); + $form->removeElement('user_level'); + } + } else { + $form->addRule('password', 'That is not the correct password', 'callback', array(&$this, 'checkPassword')); + $form->addRule(array('password', 'confirm_password'), 'The passwords do not match', 'compare'); + } + parent::postGenerateForm($form); + } + + function preProcessForm(&$values) { + $current_user_level = $this->_auth->getAuthData('user_level'); + if ($current_user_level >= N_USER_ADMIN && isset($values['new_password']) && $values['new_password']) { + $values['password'] = md5($values['new_password']); + } else { + if (isset($values['password']) && isset($values['new_password']) && $values['new_password']) { + $values['password'] = md5($values['new_password']); + } else if (isset($values['password']) && isset($values['new_password']) && !$values['new_password']) { + $values['password'] = null; + } else if (!$values['id']) { + $values['password'] = md5($values['password']); + } + } + parent::preProcessForm($values); + } + + function validateEdit($values) { + $model = &$this->getDefaultModel(); + $errors = array(); + if (isset($values['new_password'])) { + if ($values['new_password'] && !$values['password']) { + $errors['password'] = 'You must type in the current password in order to change it.'; + } else if ($values['new_password'] && $values['password']) { + if ($model->password != md5($values['password'])) { + $errors['password'] = 'That is not the correct password'; + } + } + } + return empty($errors)?true:$errors; + } + + function loadSubnav($parameter) { + $current_user_level = $this->_auth->getAuthData('user_level'); + if ($current_user_level >= N_USER_ADMIN) { + parent::loadSubnav($parameter); + } + } + + /** + * passwordEmail - Email the passed password back to the user in $model_array['email'] + * + * @param array cms_user array + * @param string The password for the user. + * @return void + **/ + function passwordEmail($model_array, $password) { + $this->set($model_array); + $this->set('password', $password); + $this->set('public_site', PUBLIC_SITE); + + // set up and send the email + $email_message = $this->render(array('action'=>'password_email', 'return'=>true)); + $email_from = 'website@' . $_SERVER['SERVER_NAME']; + $email_to = $model_array['email']; + $email_subject = SITE_NAME . ' - Forgotten Password'; + mail($email_to, $email_subject, $email_message); + } + + /** + * sendConfirmationEmail - Sends a confirmation email for a password reset. + * + * @param string The email address to send the email to. + * @return void + **/ + function sendConfirmationEmail($email) { + $cms_auth = NModel::factory('cms_auth'); + if ($confirmation_token = $cms_auth->getConfirmationToken($email)) { + $this->set('confirmation_token', $confirmation_token); + $this->set('ip', $_SERVER['REMOTE_ADDR']); + $this->set('public_site', PUBLIC_SITE); + + // set up and send the email + $email_message = $this->render(array('action'=>'confirmation_email', 'return'=>true)); + $email_from = 'website@' . $_SERVER['SERVER_NAME']; + $email_to = $email; + $email_subject = SITE_NAME . ' - Confirm Password Reset'; + mail($email_to, $email_subject, $email_message); + } + } + +} +?> \ No newline at end of file diff --git a/app/controllers/version_check_controller.php b/app/controllers/version_check_controller.php new file mode 100644 index 0000000..1c19917 --- /dev/null +++ b/app/controllers/version_check_controller.php @@ -0,0 +1,109 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since 3.1.12 + */ +class VersionCheckController extends AssetController { + var $check_version_url = 'https://raw.githubusercontent.com/nonfiction/nterchange/master/version.yml'; + var $check_version_interval = 86400; + var $cache_name = 'version_check'; + var $cache_group = 'vcheck'; + + function __construct() { + $this->name = 'version_check'; + $this->base_view_dir = ROOT_DIR; + parent::__construct(); + } + + /** + * versionCheck - Get the most current version of nterchange and cache the result. + * + * @return array Information about the newest version of nterchange. + **/ + function versionCheck() { + require_once 'Cache/Lite.php'; + $options = array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>$this->check_version_interval); + $cache = new Cache_Lite($options); + $yaml = $cache->get($this->cache_name, $this->cache_group); + if (empty($yaml)) { + include_once 'HTTP/Request.php'; + $req = new HTTP_Request($this->check_version_url); + if (!PEAR::isError($req->sendRequest())) { + $yaml = $req->getResponseBody(); + $cached = $cache->save($yaml, $this->cache_name, $this->cache_group); + if ($cached == true) { + NDebug::debug('Version check - data is from the web and is now cached.' , N_DEBUGTYPE_INFO); + } else { + NDebug::debug('Version check - data is from the web and is NOT cached.' , N_DEBUGTYPE_INFO); + } + } + } else { + NDebug::debug('Version check - data is from the cache.' , N_DEBUGTYPE_INFO); + } + require_once 'vendor/spyc.php'; + $newest_version_info = @Spyc::YAMLLoad($yaml); + return $newest_version_info; + } + + /** + * compareVersions - Compares the current nterchange version to the newest version. + * + * @param string Current version of nterchange. + * @param string New version of nterchange. + * @return boolean + **/ + function compareVersions($current, $new) { + $comparison = version_compare($current, $new); + if ($comparison == -1) { + return true; + } else { + return false; + } + } + + /** + * dashboardVersionCheck - This runs for ADMIN users or higher and lets them know + * if there is an upgrade available for nterchange. Called from the dashboard + * helper and displays on the dashboard. + * + * @return void + **/ + function dashboardVersionCheck() { + // Check the user level - this only shows up for admins or higher. + $auth = new NAuth(); + $current_user_level = $auth->getAuthData('user_level'); + unset($auth); + if ($current_user_level >= N_USER_ADMIN) { + $newest = $this->versionCheck(); + if (is_array($newest)) { + $upgrade = $this->compareVersions(NTERCHANGE_VERSION, $newest['version']); + if ($upgrade == true) { + $this->set('upgrade', $newest); + $this->set('nterchange_version', NTERCHANGE_VERSION); + } else { + $this->set('uptodate', true); + } + $this->render(array('action'=>'dashboard_version_check', 'return'=>false)); + } else { + NDebug::debug('There was an error with the version check.' , N_DEBUGTYPE_INFO); + } + } + } + +} +?> diff --git a/app/controllers/version_controller.php b/app/controllers/version_controller.php new file mode 100644 index 0000000..28d9221 --- /dev/null +++ b/app/controllers/version_controller.php @@ -0,0 +1,219 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class VersionController extends nterchangeController { + function __construct() { + $this->name = 'version'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + $this->public_actions = array('view', 'reinstate'); + $this->login_required = true; + parent::__construct(); + } + + function &getDefaultModel() { + return $this->loadModel('cms_nterchange_versions'); + } + + function view($parameter) { + if ($model = &$this->getDefaultModel() && $model->get($parameter)) { + $this->set('asset', unserialize($model->version)); + } + } + + /** + * reinstate - Pull an old version of a content asset back and insert it as the current version. + * + * @param int Version id you want reinstated. + * @return void + **/ + function reinstate($parameter) { + // pull the version and save the content into the asset record + if ($model = &$this->getDefaultModel() && $model->get($parameter)) { + $version = unserialize($model->version); + $asset_controller = &NController::factory($model->asset); + $asset_controller->_auth = &$this->_auth; + $asset_model = &$asset_controller->getDefaultModel(); + if ($asset_controller && $asset_model && $asset_model->get($model->asset_id)) { + foreach ($version as $k=>$v) { + if (!preg_match('/^cms_/', $k) && $k != 'id') { + $asset_model->$k = $v; + } + } + // insert a new version as part of the process + $asset_controller->insertVersion(); + // save the new record + $asset_model->save(); + } + unset($asset_model); + $this->flash->set('notice', 'The version has been reinstated.'); + $referer = isset($this->params['_referer'])?$this->params['_referer']:false; + if (!$referer) { + include_once 'view/helpers/url_helper.php'; + $referer = urlHelper::urlFor($this, array('controller'=>$model->asset, 'action'=>'edit', 'id'=>$model->asset_id)); + } + header('Location:' . $referer); + exit; + } + } + + /** + * fileDelete - Delete a file from an old version - but only if it's not available + * in the current version. + * + * @param array An unserialized array from cms_nterchange_versions->version + * @param string The name of an asset + * @param int The id of the asset. + * @return boolean + * @todo Make this work with n_mirror. + **/ + function fileDelete($array, $asset, $asset_id) { + $current_version = $this->getCurrentVersion($asset, $asset_id); + if (!$current_version) return false; + foreach ($array as $field) { + $ereg = '^' . UPLOAD_DIR; + if (eregi($ereg, $field)) { + // Check to see whether or not the file can be deleted. + // If it was uploaded in a previous version, but is still available in the + // current version, we don't want to delete it. + if (in_array($field, $current_version)) { + NDebug::debug('Cannot delete ' . $field . ' as it is in the current version.' , N_DEBUGTYPE_INFO); + } else { + $ret = NFilesystem::deleteFile($field); + } + } + } + return $ret; + } + + /** + * deleteEmptyFolders - Delete folders without anything in them anymore. + * + * @param string The name of the asset. + * @param int The id of that asset. + * @return void + * @todo Make this work with n_mirror. + **/ + function deleteEmptyFolders($asset_name, $asset_id) { + $folder = DOCUMENT_ROOT . UPLOAD_DIR . '/' . $asset_name . '/' . $asset_id; + if (is_dir($folder)) { + if ($handle = opendir($folder)) { + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + if (is_dir($folder . '/' . $file)) { + $full_path = $folder . '/' . $file; + // Check to see if it's empty. + if (count(glob("$full_path/*")) === 0) { + // Then delete if this is so. + NFilesystem::deleteFolder($full_path); + } else { + NDebug::debug("$full_path is not empty and will not be deleted." , N_DEBUGTYPE_INFO); + } + + } + } + } + closedir($handle); + } + } + } + + /** + * getCurrentVersion - Get the current version of the $asset referenced by $asset_id + * + * @param string The name of the asset. + * @param int The id of that asset. + * @return array All the content in that asset. + **/ + function getCurrentVersion($asset, $asset_id) { + $asset_object = NModel::factory($asset); + $asset_object->id = $asset_id; + if ($asset_object->find()) { + while ($asset_object->fetch()) { + $arr = $asset_object->toArray(); + return $arr; + } + } else { + return false; + } + } + + /** + * deleteAll - Delete all old versions of a particular asset. + * This comprises all files and empty folders as well. + * + * @param int The id of this particular version. + * @return void + **/ + function deleteAll($parameter) { + if ($model = &$this->getDefaultModel() && $model->get($parameter)) { + $info = $model->toArray(); + $model->reset(); + // Let's get all the versions for that asset and asset_id. + $model->asset_id = $info['asset_id']; + $model->asset = $info['asset']; + $this->debug("Deleting versions for {$model->asset} : {$model->asset_id}"); + if ($model->find()) { + while ($model->fetch()) { + $arr = $model->toArray(); + $content = unserialize($arr['version']); + $success = $this->fileDelete($content, $info['asset'], $info['asset_id']); + $model->delete(); + } + } + $this->deleteEmptyFolders($info['asset'], $info['asset_id']); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$info['asset'], 'asset_id'=>$info['asset_id'], 'action_taken'=>AUDIT_ACTION_DELETE_ASSET_VERSIONS)); + unset($audit_trail); + } + header('Location:' . $_GET['_referer']); + } + } + + /** + * deleteEverything - Delete all old versions of everything. Used just before + * website launch or when somebody just wants to clean up. + * NOTE: Not linked to from anywhere or really heavily tested. Use with caution. + * + * @return void + **/ + function deleteEverything($parameter) { + if ($model = &$this->getDefaultModel()) { + if ($model->find()) { + while ($model->fetch()) { + $arr = $model->toArray(); + $content = unserialize($arr['version']); + $success = $this->fileDelete($content, $arr['asset'], $arr['asset_id']); + $this->deleteEmptyFolders($arr['asset'], $arr['asset_id']); + $model->delete(); + } + // Let's empty out the entire table as well. + $sql = 'TRUNCATE cms_nterchange_versions'; + $db = NDB::connect(); + $res = $db->query($sql); + } else { + NDebug::debug('There were no old versions of anything to delete.' , N_DEBUGTYPE_INFO); + } + header('Location:/nterchange/'); + } + } +} +?> diff --git a/app/controllers/workflow_controller.php b/app/controllers/workflow_controller.php new file mode 100644 index 0000000..8988b8f --- /dev/null +++ b/app/controllers/workflow_controller.php @@ -0,0 +1,912 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class WorkflowController extends nterchangeController { + var $workflow_users = array(); + + function __construct() { + $this->name = 'workflow'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_NORIGHTS; + // this whole controller requires login if called directly + $this->login_required = true; + $this->base_dir = APP_DIR; + parent::__construct(); + } + + function index() { + $this->redirectTo(array('dashboard')); + } + + function viewlist() { + $this->redirectTo(array('dashboard')); + } + + function process($workflow_id) { + $this->auto_render = false; + $model = &$this->getDefaultModel(); + $model->completed = 0; + // get the workflow info + if (!$model->get($workflow_id)) { + $this->redirectTo('index'); + } + // push to the client's time zone + $this->convertDateTimesToClient($model); + // load the workflow group + $workflow_group = &$model->getLink('workflow_group_id', 'workflow_group'); + // get the asset info for the email + $asset_ctrl = &NController::factory($model->asset); + $asset_model = &$asset_ctrl->getDefaultModel(); + if(!$asset_ctrl || !$asset_model->get($model->asset_id)) { + $this->redirectTo('index'); + } + $asset_name = $asset_ctrl->page_title?$asset_ctrl->page_title:Inflector::humanize($asset_ctrl->name); + // load the workflow draft into the asset_model for the form + $workflow_values = unserialize($model->draft); + foreach ($workflow_values as $field=>$val) { + $asset_model->$field = $val; + } + // check for the content being linked to any other pages + $page_content_model = &NModel::factory('page_content'); + $page_contents = &$page_content_model->getActivePageContent($asset_ctrl->name, $asset_model->{$asset_model->primaryKey()}); + $pages = false; + if ($model->action == WORKFLOW_ACTION_EDIT && $page_contents && count($page_contents) > 1) { + $pages = array(); + $tmp_page_model = clone($page_model); + foreach ($page_contents as $page_content) { + if (!isset($pages[$tmp_page_model->{$tmp_page_model->primaryKey()}])) { + $tmp_page_model->reset(); + $tmp_page_model->get($page_content->page_id); + $pages[$tmp_page_model->{$tmp_page_model->primaryKey()}] = $tmp_page_model->toArray(); + } + } + if (count($pages) == 1) { + $pages = false; + } + unset($tmp_page_model); + } + unset($page_contents); + $this->set('pages', $pages); + unset($pages); + // get the page info for the email + $page_content_model = &$model->getLink('page_content_id', 'page_content'); + $page = &NController::singleton('page'); + $page_model = $page->getDefaultModel(); + if (!$page_model->get($page_content_model->page_id)) { + $this->redirectTo('index'); + } + // set up urls + $public_site = preg_replace('|/$|', '', PUBLIC_SITE); + $admin_site = preg_replace('|/$|', '', (defined('ADMIN_URL') && ADMIN_URL?ADMIN_URL:PUBLIC_SITE)); + $live_url = $public_site . $page->getHref($page_model->toArray()); + $page->nterchange = true; + $preview_url = $admin_site . $page->getHref($page_model->toArray()); + // user rights + $user_rights = &$this->getWorkflowUserRights($page_model); + if ($user_rights & WORKFLOW_RIGHT_EDIT && !($user_rights & WORKFLOW_RIGHT_APPROVE)) { + // only an author, can't approve anything + $this->redirectTo('index'); + } + $description = ''; + switch ($model->action) { + case WORKFLOW_ACTION_EDIT: + $description = 'This content is being edited on the page.'; + break; + case WORKFLOW_ACTION_DELETE: + $description = 'This content is being deleted.'; + break; + case WORKFLOW_ACTION_ADDNEW: + $description = 'This is new content being added to the page.'; + break; + case WORKFLOW_ACTION_ADDEXISTING: + $description = 'This is existing content being added to the page.'; + break; + case WORKFLOW_ACTION_REMOVE: + $description = 'The content is being removed from the page.'; + break; + } + $this->set('description', $description); + // set up the form + $cform = new ControllerForm($asset_ctrl, $asset_model); + $form = &$cform->getForm(); + if (SITE_DRAFTS) { + // remove the submit draft button since we're in workflow + $form->removeElement('__submit_draft__'); + } + // remove the submit button since we're in workflow + $form->removeElement('__submit__'); + // get the workflow form + $wcform = new ControllerForm($this, $model); + $wform = &$wcform->getForm(); + $submit = &$wform->getElement('__submit__'); + $submit->setName('__submit_workflow__'); + if ($user_rights & WORKFLOW_RIGHT_PUBLISH) { + $submit->setValue('Submit & Publish'); + } else { + $submit->setValue('Submit'); + } + // add workflow form to the existing form + foreach ($wform->_elements as $el) { + $form->addElement($el); + } + $form->addFormRule(array(&$this, 'validateWorkflowProcess')); + /* + if (!($user_rights & WORKFLOW_RIGHT_EDIT)) { + $fields = $asset_model->fields(); + foreach ($fields as $i=>$field) { + if ($form->elementExists($field)) { + $el = &$form->getElement($field); + $el->freeze(); + } + } + } + */ + if ($model->action == WORKFLOW_ACTION_REMOVE) { + $fields = $asset_model->fields(); + $form->removeElement('timed_start'); + $form->removeElement('timed_end'); + $form->freeze($fields); + } + + // act on the submission + if ($form->validate()) { + $values = $form->getSubmitValues(); + $model->comments = $values['comments']; + $fields = $asset_model->fields(); + $draft_values = array(); + foreach ($fields as $field) { + if (isset($values[$field])) + $draft_values[$field] = $values[$field]; + } + $auth = new NAuth(); + $user_id = $auth->currentUserID(); + unset($auth); + + if ($values['workflow_approve'] == 0) { + // if not approved, then email the original user and set the workflow unsubmitted + if ($parent_workflow = $model->parent_workflow) { + $model->delete(); + $model->reset(); + $model->get($parent_workflow); + $model->approved = 0; + } else { + $model->submitted = 0; + } + if (isset($draft_values)) { + $model->draft = serialize($draft_values); + } + $model->comments = $values['comments']; + include_once 'n_date.php'; + $model->cms_created = NDate::convertTimeToUTC($model->cms_created, '%Y-%m-%d %H:%M:%S'); + $model->cms_modified = NDate::convertTimeToUTC($model->cms_modified , '%Y-%m-%d %H:%M:%S'); + $model->update(); + $current_user = &NModel::factory('cms_auth'); + $current_user->get($user_id); + $user_model = &NModel::factory('cms_auth'); + $user_model->get($model->cms_modified_by_user); + include_once 'Mail.php'; + $mail = &Mail::factory('mail', "-f{$current_user->email}"); + $headers['From'] = "{$current_user->real_name} <{$current_user->email}>"; + $headers['Subject'] = 'Website: "' . $workflow_group->workflow_title . '" Workflow Group has content that was declined'; + $msg = ''; + $msg .= "The workflow for the \"{$asset_model->cms_headline}\" {$asset_name} record on the {$page_model->title} page was declined.\n\n"; + $msg .= "COMMENTS:\n{$model->comments}\n\n"; + $msg .= "You can view the current live page at:\n$live_url\n\n"; + $msg .= "You can preview the page at:\n$preview_url\n\n"; + $msg .= "To Edit & Resubmit your changes, please go to Your Dashboard:\n" . $admin_site . '/' . APP_DIR . "/dashboard\n\n"; + $this->set('public_site', $public_site); + $this->set('admin_site', $admin_site); + // gather the users + $recipients = array(); + $email = "{$user_model->real_name} <{$user_model->email}>"; + $recipients[] = $email; + $mail->send($recipients, $headers, $msg); + unset($mail); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$model->asset, 'asset_id'=>$model->asset_id, 'action_taken'=>AUDIT_ACTION_WORKFLOW_DECLINE, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$model->workflow_group_id, 'page_id'=>$model->page_id, 'page_content_id'=>$model->page_content_id)); + unset($audit_trail); + } + $this->render(array('layout'=>'plain', 'action'=>'disapproved')); + exit; + } else { + // if approved, then set any comments, set the workflow approved, insert a new one + // either submit it (which emails it to the next users) + // or publish it (if there are no next users) + if ($user_rights & WORKFLOW_RIGHT_PUBLISH) { + // publish by pulling the draft, updating the original asset and marking page_content.cms_workflow=0 + if (isset($draft_values)) { + foreach ($draft_values as $field=>$val) { + $asset_model->$field = $val; + } + } + $asset_model->cms_modified = $asset_model->now(); + $asset_model->cms_modified_by_user = $user_id; + $asset_model->cms_draft = 0; + $asset_model->update(); + // kill a draft if one exists + $workflow_model = &NModel::factory('workflow'); + $workflow_model->parent_workflow = 0; + $workflow_model->asset = $model->asset; + $workflow_model->asset_id = $model->asset_id; + $workflow_model->workflow_group_id = $model->workflow_group_id; + if ($workflow_model->find(null, true)) { + // delete drafts related to this workflow + $draft_model = &NModel::factory('cms_drafts'); + $draft_model->asset = $model->asset; + $draft_model->asset_id = $model->asset_id; + $draft_model->cms_modified_by_user = $workflow_model->cms_modified_by_user; + if ($draft_model->find()) { + while ($draft_model->fetch()) { + $draft_model->delete(); + } + } + } + if ($workflow_model->action == WORKFLOW_ACTION_REMOVE) { + // delete the page_content to detach + if ($page_content_model->{$page_content_model->primaryKey()}) { + $page_content_model->delete(); + } + unset($page_content_model); + $page->deletePageCache($model->page_id); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$model->asset, 'asset_id'=>$model->asset_id, 'action_taken'=>AUDIT_ACTION_WORKFLOW_APPROVEREMOVE, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$model->workflow_group_id, 'page_id'=>$model->page_id, 'page_content_id'=>$model->page_content_id)); + unset($audit_trail); + } + $this->completeWorkflow($workflow_id); + $this->set('workflow_group', $workflow_group->toArray()); + $this->set('asset_name', $asset_name); + $this->set('asset', $asset_model->toArray()); + $this->set('page', $page_model->toArray()); + $this->render(array('action'=>'removed', 'layout'=>'default')); + exit; + } else { + // update the page_content row + if ($page_content_model->{$page_content_model->primaryKey()}) { + $timed_start = NDate::arrayToDate($values['timed_start']); + $timed_end = NDate::arrayToDate($values['timed_end']); + if (!NDate::validDateTime($timed_start)) { + $timed_start = 'null'; + } + if (!NDate::validDateTime($timed_end)) { + $timed_end = 'null'; + } + // set the timed content in page_content + $page_content_model->timed_start = $timed_start; + $page_content_model->timed_end = $timed_end; + // set page_content cms_workflow to 0, allowing the content to show up + $page_content_model->cms_workflow = 0; + $page_content_model->update(); + } + unset($page_content_model); + $this->completeWorkflow($workflow_id); + $page->deletePageCache($page_model->{$page_model->primaryKey()}); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$model->asset, 'asset_id'=>$model->asset_id, 'action_taken'=>AUDIT_ACTION_WORKFLOW_APPROVEPUBLISH, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$model->workflow_group_id, 'page_id'=>$model->page_id, 'page_content_id'=>$model->page_content_id)); + unset($audit_trail); + } + $this->set('workflow_group', $workflow_group->toArray()); + $this->set('asset_name', $asset_name); + $this->set('asset', $asset_model->toArray()); + $this->set('page', $page_model->toArray()); + $this->render(array('action'=>'published', 'layout'=>'default')); + exit; + } + } else { + $model->approved = 1; + $model->comments = $values['comments']; + $timed_start = isset($values['timed_start'])?NDate::arrayToDate($values['timed_start']):null; + $timed_end = isset($values['timed_end'])?NDate::arrayToDate($values['timed_end']):null; + if (!NDate::validDateTime($timed_start)) { + $timed_start = 'null'; + } + if (!NDate::validDateTime($timed_end)) { + $timed_end = 'null'; + } + $model->timed_start = $timed_start; + $model->timed_end = $timed_end; + include_once 'n_date.php'; + $model->cms_created = NDate::convertTimeToUTC($model->cms_created, '%Y-%m-%d %H:%M:%S'); + $model->cms_modified = NDate::convertTimeToUTC($model->cms_modified , '%Y-%m-%d %H:%M:%S'); + $model->update(); + $parent_workflow = $model->{$model->primaryKey()}; + $new_workflow = &NModel::factory($this->name); + $new_workflow->page_id = $model->page_id; + $new_workflow->page_content_id = $model->page_content_id; + $new_workflow->workflow_group_id = $model->workflow_group_id; + $new_workflow->asset = $model->asset; + $new_workflow->asset_id = $model->asset_id; + $new_workflow->action = $model->action; + $new_workflow->draft = serialize($draft_values); + $new_workflow->comments = $values['comments']; + $new_workflow->timed_start = $timed_start; + $new_workflow->timed_end = $timed_end; + $new_workflow->parent_workflow = $parent_workflow; + $new_workflow->cms_created = $new_workflow->now(); + $new_workflow->cms_modified = $new_workflow->now(); + $new_workflow->cms_modified_by_user = $user_id; + $new_workflow->insert(); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$model->asset, 'asset_id'=>$model->asset_id, 'action_taken'=>AUDIT_ACTION_WORKFLOW_APPROVE, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$model->workflow_group_id, 'page_id'=>$model->page_id, 'page_content_id'=>$model->page_content_id)); + unset($audit_trail); + } + $this->flash->set('notice', 'Your workflow step has been submitted and notifications have been sent.'); + $this->redirectTo('submit', $new_workflow->{$new_workflow->primaryKey()}); + } + } + } + unset($page_content_model); + // set up the view + $this->set('workflow_group', $workflow_group->toArray()); + $this->set('asset_name', $asset_name); + $this->set('asset', $asset_model->toArray()); + $this->set('page', $page_model->toArray()); + $this->set('form', $form->toHTML()); + $this->render(array('layout'=>'plain', 'action'=>'process')); + } + + function validateWorkflowProcess($values) { + $errors = array(); + if ($values['workflow_approve'] == 0 && !$values['comments']) { + $errors['comments'] = 'You must supply comments if you do not approve the workflow'; + } + return count($errors)?$errors:true; + } + + function completeWorkflow($workflow_id) { + if (!$workflow_id) return; + $model = &NModel::factory($this->name); + if ($model->get($workflow_id)) { + $parent_workflow = $model->parent_workflow; + $model->approved = 1; + $model->completed = 1; + $model->update(); + $this->completeWorkflow($parent_workflow); + } + unset($model); + return true; + } + + function submit($workflow_id) { + if (false !== strpos($workflow_id, ';')) { + $workflow_id = explode(';', $workflow_id); + } else { + $workflow_id = array($workflow_id); + } + $this->auto_render = false; + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + // get the workflow info + $workflows = array(); + foreach ($workflow_id as $id) { + $model->reset(); + $model->submitted = 0; + if ($model->get($id)) { + $this->convertDateTimesToClient($model); + $workflows[] = clone($model); + } + } + if (empty($workflows)) { + $this->redirectTo('index'); + } + $html = ''; + foreach ($workflows as $model) { + // push the times back so they can get pushed forward again on update() + // get the asset info for the email + $asset_ctrl = &NController::factory($model->asset); + $asset_model = &NModel::factory($model->asset); + if(!$asset_ctrl || !$asset_model->get($model->asset_id)) { + $this->redirectTo('index'); + } + $asset_name = $asset_ctrl->page_title?$asset_ctrl->page_title:Inflector::humanize($asset_ctrl->name); + // get the page info for the email + $page_content_model = $workflows[0]->getLink('page_content_id', 'page_content'); + $page = &NController::singleton('page'); + $page_model = &$page->getDefaultModel(); + $page_model->reset(); + if (!$page_model->get($page_content_model->page_id)) { + $this->redirectTo('index'); + } + $live_url = preg_replace('/\/$/', '', PUBLIC_SITE) . $page->getHref($page_model->toArray()); + $page->nterchange = true; + $preview_url = preg_replace('/\/$/', '', (defined('ADMIN_URL') && ADMIN_URL?ADMIN_URL:PUBLIC_SITE)) . $page->getHref($page_model->toArray()); + $dashboard_url = preg_replace('/\/$/', '', (defined('ADMIN_URL') && ADMIN_URL?ADMIN_URL:PUBLIC_SITE)) . '/' . APP_DIR . '/dashboard'; + $auth = new NAuth(); + $user_model = &NModel::factory('cms_auth'); + $user_model->get($auth->currentUserId()); + unset($auth); + $workflow_group = &$workflows[0]->getLink('workflow_group_id', 'workflow_group'); + // get the users + $user_rights = $this->getWorkflowUserRights($page_model, $user_model->{$user_model->primaryKey()}); + $users = $this->getNotifyUsers($model->$pk, $user_rights); + +/* +varDump('###################'); +varDump($user_rights); +foreach ($users as $user) { + varDump($user->toArray()); +} +exit; +*/ + + include_once 'Mail.php'; + $mail = &Mail::factory('mail', "-f{$user_model->email}"); + $headers['From'] = "{$user_model->real_name} <{$user_model->email}>"; + $headers['Subject'] = 'Website: "' . $workflow_group->workflow_title . '" Workflow Group has content waiting for your approval'; + // $headers['To'] = ''; + $msg = ''; + $msg .= "The workflow for the \"{$asset_model->cms_headline}\" {$asset_name} record on the {$page_model->title} page is awaiting your approval.\n\n"; + if ($model->comments) { + $msg .= "COMMENTS:\n{$model->comments}\n\n"; + } + $msg .= "You can view the current live page at:\n$live_url\n\n"; + $msg .= "You can preview the page at:\n$preview_url\n\n"; + $msg .= "To Approve/Decline the changes, please go to Your Dashboard:\n" . $dashboard_url; + // gather the users + $user_array = array(); + $recipients = array(); + foreach ($users as $user) { + if ($user->{$user->primaryKey()} == $user_model->{$user_model->primaryKey()}) continue; + $email = "{$user->real_name} <{$user->email}>"; + $recipients[] = $email; + // $headers['To'] .= ($headers['To']?', ':'') . $email; + $user_array[] = $user->toArray(); + } + if (!empty($recipients)) { + $mail->send($recipients, $headers, $msg); + } + unset($mail); + // update the workflow and set submitted to true + $model->submitted = 1; + include_once 'n_date.php'; + $model->cms_created = NDate::convertTimeToUTC($model->cms_created, '%Y-%m-%d %H:%M:%S'); + $model->cms_modified = NDate::convertTimeToUTC($model->cms_modified , '%Y-%m-%d %H:%M:%S'); + $model->update(); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$model->asset, 'asset_id'=>$model->asset_id, 'action_taken'=>AUDIT_ACTION_WORKFLOW_SUBMIT, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$model->workflow_group_id, 'page_id'=>$model->page_id, 'page_content_id'=>$model->page_content_id)); + unset($audit_trail); + } + // set up the view + $this->set('asset_name', $asset_name); + $this->set('asset', $asset_model->toArray()); + $this->set('workflow_group', $workflow_group->toArray()); + $this->set('page', $page_model->toArray()); + $this->set('users', $user_array); + $html .= $this->render(array('action'=>'send', 'return'=>true)); + } + $this->set('MAIN_CONTENT', $html); + $this->render(array('layout'=>'default')); + } + + function actionToString($action) { + switch ($action) { + case WORKFLOW_ACTION_ADDEXISTING: + $action = 'added'; + break; + case WORKFLOW_ACTION_ADDNEW: + $action = 'created and added'; + break; + case WORKFLOW_ACTION_REMOVE: + $action = 'removed'; + break; + case WORKFLOW_ACTION_EDIT: + $action = 'edited'; + break; + case WORKFLOW_ACTION_DELETE: + $action = 'deleted'; + break; + } + return $action; + } + + function getNotifyUsers($workflow_id, $user_rights) { + $model = &NModel::factory($this->name); + $pk = $model->primaryKey(); + if ($model->get($workflow_id)) { + $page_model = &$model->getLink('page_id', 'page'); + $model_count = clone($model); + $model_count->reset(); + $parent_workflow = $model->parent_workflow?$model->parent_workflow:$workflow_id; + $workflow_steps = array(); + if ($model_count->find(array('conditions'=>$pk . '=' . $parent_workflow . ' OR parent_workflow=' . $parent_workflow))) { + $workflow_steps = &$model_count->fetchAll(); + } + // we need to figure out who to email to... + $notify_users = array(); + if ($page_model = &$model->getLink('page_id', 'page')) { + $user_model = &NModel::factory('cms_auth'); + $pk = $user_model->primaryKey(); + // + $users = array(); + $workflow_user = &NModel::factory('workflow_users'); + $workflow_user->workflow_group_id = $model->workflow_group_id; + $auth = new NAuth(); + if ($workflow_user->find()) { + $author = false; + $editor = false; + $approver = false; + while ($workflow_user->fetch()) { + if ($user_model->get($workflow_user->user_id)) { + switch ($workflow_user->role) { + case WORKFLOW_ROLE_AUTHOR: + $author = true; + break; + case WORKFLOW_ROLE_EDITOR: + $editor = true; + break; + case WORKFLOW_ROLE_APPROVER: + $approver = true; + break; + } + $users[] = clone($user_model); + } + $user_model->reset(); + } + foreach ($users as $user) { + $notify_user_rights = $this->getWorkflowUserRights($page_model, $user->{$user->primaryKey()}); + switch (1) { + case count($workflow_steps) == 1: + $rights_needed = WORKFLOW_RIGHT_EDIT + WORKFLOW_RIGHT_APPROVE; + if ($notify_user_rights >= $rights_needed) { + if ($author && $editor && $approver && !($notify_user_rights & WORKFLOW_RIGHT_PUBLISH)) { + $notify_users[] = $user; + } else if ((!$author || !$editor || !$approver) && $notify_user_rights & WORKFLOW_RIGHT_APPROVE) { + $notify_users[] = $user; + } + } + break; + case count($workflow_steps) == 2 && $workflow_steps[1]->approved: + $rights_needed = WORKFLOW_RIGHT_APPROVE + WORKFLOW_RIGHT_PUBLISH; + if ($notify_user_rights >= $rights_needed) { + $notify_users[] = $user; + } + break; + } + } + unset($users); + } + if (empty($notify_users) && $user_model->get($auth->currentUserId()) && $user_model->user_level >= N_USER_ADMIN) { + $notify_users[] = clone($user_model); + } + unset($user_model); + unset($page_content_model); + unset($page_model); + return $notify_users; + } + } + return false; + } + + function &getWorkflowGroup(&$page_model) { + if (!$page_model) return false; + $workflow_group_id = 0; + if ($page_model->workflow_group_id) { + // look no further, this is all you need + $workflow_group_id = $page_model->workflow_group_id; + } else { + // find the first ancestor with a workflow_group_id that is also recursive + $ancestors = $page_model->getAncestors($page_model->{$page_model->primaryKey()}, false, false); + foreach ($ancestors as $ancestor) { + if ($ancestor['workflow_group_id'] && $ancestor['workflow_recursive']) { + $workflow_group_id = $ancestor['workflow_group_id']; + break; + } + } + } + if ($workflow_group_id) { + $model = &NModel::factory('workflow_group'); + if ($model && $model->get($workflow_group_id)) { + return $model; + } + } + $model = false; + return $model; + } + + function getWorkflowUserRights(&$page_model, $user_id=null) { + $model = &NModel::factory('workflow_users'); + if (!$model) { + return false; + } + $auth = new NAuth; + if (!$user_id) { + $user_id = $auth->currentUserID(); + } + $user_model = &$this->loadModel('cms_auth'); + $user_model->get($user_id); + $rights = 0; + $workflow_group = &$this->getWorkflowGroup($page_model); + if ($workflow_group) { + $workflow_group_id = $workflow_group->{$workflow_group->primaryKey()}; + $model->workflow_group_id = $workflow_group_id; + $model->user_id = $user_id; + if ($model->find(null, true)) { + $workflow_user_model = clone($model); + $current_role = $workflow_user_model->role; + $rights = 0; + $model->reset(); + $model->workflow_group_id = $workflow_group_id; + if ($model->find()) { + $author = false; + $editor = false; + $approver = false; + while ($model->fetch()) { + switch ($model->role) { + case WORKFLOW_ROLE_AUTHOR: + $author = true; + break; + case WORKFLOW_ROLE_EDITOR: + $editor = true; + break; + case WORKFLOW_ROLE_APPROVER: + $approver = true; + break; + } + } + switch (1) { + case $current_role == WORKFLOW_ROLE_AUTHOR: + $rights = WORKFLOW_RIGHT_EDIT; + if (!$editor) { + $rights += WORKFLOW_RIGHT_APPROVE; + } + if (!$editor && !$approver) { + $rights += WORKFLOW_RIGHT_PUBLISH; + } + break; + case $current_role == WORKFLOW_ROLE_EDITOR: + $rights = WORKFLOW_RIGHT_EDIT + WORKFLOW_RIGHT_APPROVE; + if (!$approver) { + $rights += WORKFLOW_RIGHT_PUBLISH; + } + break; + case $current_role == WORKFLOW_ROLE_APPROVER: + $rights = WORKFLOW_RIGHT_APPROVE + WORKFLOW_RIGHT_PUBLISH; + break; + } + } + } + if ($user_model->user_level >= N_USER_ADMIN && !($rights & WORKFLOW_RIGHT_EDIT)) { + $rights += WORKFLOW_RIGHT_EDIT; + } + } + unset($model); + return $rights; + } + + function getWorkflowUsers($workflow_group_id) { + $model = &NModel::factory('workflow_users'); + if ($model) { + $model->workflow_group_id = $workflow_group_id; + if ($model->find()) { + return $model->fetchAll(); + } + } + return false; + } + + function getWorkflowUser($workflow_group_id) { + $model = &$this->loadModel('workflow_users'); + if ($model) { + $auth = new NAuth(); + $current_user = $auth->currentUserID(); + $model->workflow_group_id = $workflow_group_id; + $model->user_id = $current_user; + if ($model->find(null, true)) { + return $model; + } + } + return false; + } + + function findContentWorkflowGroup(&$asset_controller) { + if (!$asset_controller) return false; + $asset_model = &$asset_controller->getDefaultModel(); + $pk = $asset_model->primaryKey(); + if (!$asset_model->$pk) return false; + $page_content_model = &$asset_controller->loadModel('page_content'); + if ($page_content_model->find(array('conditions'=>'content_asset=\'' . $asset_controller->name . '\' AND content_asset_id=' . $asset_model->$pk))) { + $page_model = &$this->loadModel('page'); + while ($page_content_model->fetch()) { + // reset before every loop so the get call will work + $page_model->reset(); + if ($page_model->get($page_content_model->page_id)) { + if ($workflow_group_model = $this->getWorkflowGroup($page_model)) { + unset($page_model); + unset($page_content_model); + return $workflow_group_model; + } + } + } + unset($page_model); + } + unset($page_content_model); + return false; + } + + function findContentWorkflow($workflow_group_id, &$asset_controller) { + if (!$workflow_group_id || !$asset_controller) return false; + $asset_model = &$asset_controller->getDefaultModel(); + $pk = $asset_model->primaryKey(); + if (!$asset_model->$pk) return false; + $page_content_model = &$asset_controller->loadModel('page_content'); + $page_content_model->reset(); + if ($page_content_model->find(array('conditions'=>'content_asset=\'' . $asset_controller->name . '\' AND content_asset_id=' . $asset_model->$pk))) { + while ($page_content_model->fetch()) { + $page_model = &$page_content_model->getLink('page_id', 'page'); + if ($page_model) { + if ($workflow = &$this->getWorkflow($page_content_model->{$page_content_model->primaryKey()}, $workflow_group_id, $asset_controller)) { + unset($page_content_model); + return $workflow; + } + } + unset($page_model); + } + } + unset($page_content_model); + return false; + } + + function saveWorkflow($values, $action, &$asset_controller) { + if (!isset($values['page_content_id']) || !$values['page_content_id'] || !isset($values['workflow_group_id']) || !$values['workflow_group_id']) { + return false; + } + // instantiate workflow model + $model = &$this->getDefaultModel(); + $table = $model->table(); + // load values + $page_content_id = $values['page_content_id']; + $page_content_model = &NModel::factory('page_content'); + $page_content_model->get($page_content_id); + $page_id = (int) $page_content_model->page_id; + unset($page_content_model); + $workflow_group_id = $values['workflow_group_id']; + // timed content + $timed_start = isset($values['timed_start']) && $values['timed_start']?$values['timed_start']:null; + $timed_end = isset($values['timed_end']) && $values['timed_end']?$values['timed_end']:null; + if ($timed_start == 'null') $timed_start = null; + if ($timed_end == 'null') $timed_end = null; + if ($timed_start && is_array($timed_start)) { + $def = $table['timed_start']; + $timed_start = NDate::arrayToDate($timed_start); + if (!($def & N_DAO_NOTNULL)) { + if (!NDate::validDateTime($timed_start)) { + $timed_start = false; + } + } + } + if ($timed_end && is_array($timed_end)) { + $def = $table['timed_end']; + $timed_end = NDate::arrayToDate($timed_end); + if (!($def & N_DAO_NOTNULL)) { + if (!NDate::validDateTime($timed_end)) { + $timed_end = false; + } + } + } + // load asset + if (!$asset_controller) return false; + $asset_model = &$asset_controller->getDefaultModel(); + if (!$asset_model) return false; + $pk = $asset_model->primaryKey(); + $fields = $asset_model->fields(); + $values = array(); + foreach ($fields as $field) { + if ($field != $pk && !preg_match('|^cms_|', $field)) // don't save any of the meta content + $values[$field] = $asset_model->$field; + } + $model->reset(); + $model->page_id = $page_id; + $model->page_content_id = $page_content_id; + $model->workflow_group_id = $workflow_group_id; + $model->asset = $asset_controller->name; + $model->asset_id = $asset_model->$pk; + $model->submitted = 0; + $model->completed = 0; + if ($timed_start) $model->timed_start = $timed_start; + if ($timed_end) $model->timed_end = $timed_end; + $model->cms_modified_by_user = $asset_controller->_auth->currentUserID(); + if ($model->find(null, true)) { + $model->cms_modified = $model->now(); + $model->draft = serialize($values); + $ret = $model->update(); + } else { + $model->action = (int) $action; + $model->draft = serialize($values); + $model->cms_created = $model->now(); + $model->cms_modified = $model->now(); + $ret = $model->insert(); + } + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$asset_controller->name, 'asset_id'=>$asset_model->$pk, 'action_taken'=>AUDIT_ACTION_WORKFLOW_START, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$workflow_group_id, 'page_id'=>$page_id, 'page_content_id'=>$page_content_id)); + unset($audit_trail); + } + unset($auth); + unset($model); + return $ret; + } + + function getWorkflow($page_content_id, $workflow_group_id, &$asset_controller, $completed=0) { + if (!$asset_controller || !$asset_model = &$asset_controller->getDefaultModel()) return false; + $pk = $asset_model->primaryKey(); + $model = &NModel::factory($this->name); + $model->page_content_id = $page_content_id; + $model->workflow_group_id = $workflow_group_id; + $model->asset = $asset_controller->name; + $model->asset_id = $asset_model->$pk; + $model->completed = (int) $completed; + if ($model->find(array('order_by'=>'id DESC'), true)) { + return $model; + } + unset($model); + return false; + } + + function isWorkflow($page_content_id, $workflow_group_id, &$asset_controller) { + if (!$asset_controller || !$asset_model = &$asset_controller->getDefaultModel()) return false; + $pk = $asset_model->primaryKey(); + $model = &$this->loadModel($this->name); + $model->page_content_id = $page_content_id; + $model->workflow_group_id = $workflow_group_id; + $model->asset = $asset_controller->name; + $model->asset_id = $asset_model->$pk; + $ret = (bool) $model->find(); + unset($model); + return $ret; + } + + function delete($parameter) { + $model = &$this->getDefaultModel(); + if ($model->get($parameter)) { + if ($page_content_model = &$model->getLink('page_content_id', 'page_content') && $page_content_model->cms_workflow == 1) { + $page_content_model->delete(); + } + } + $model->reset(); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$model->asset, 'asset_id'=>$model->asset_id, 'action_taken'=>AUDIT_ACTION_WORKFLOW_DELETE, 'workflow_id'=>$model->{$model->primaryKey()}, 'workflow_group_id'=>$model->workflow_group_id, 'page_id'=>$model->page_id, 'page_content_id'=>$model->page_content_id)); + unset($audit_trail); + } + parent::delete($parameter); + } +} +?> diff --git a/app/controllers/workflow_group_controller.php b/app/controllers/workflow_group_controller.php new file mode 100644 index 0000000..2b32d53 --- /dev/null +++ b/app/controllers/workflow_group_controller.php @@ -0,0 +1,153 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class WorkflowGroupController extends nterchangeController { + function __construct() { + $this->name = 'workflow_group'; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + // this whole controller requires login if called directly + $this->login_required = true; + $this->base_dir = APP_DIR; + $this->page_title = 'Workflow Groups'; + parent::__construct(); + } + + function index($parameter) { + $this->redirectTo('viewlist', $parameter); + } + + // specialized list for workflow (integrated ajax for workflow users) + function viewlist($parameter=null) { + $this->auto_render = false; + $wusers_ctrl = &NController::singleton('workflow_users'); + include_once 'controller/form.php'; + + $this->base_dir = APP_DIR; + $model = &$this->loadModel($this->name); + $pk = $model->primaryKey(); + if ($model) { + if ($parameter) $model->{$model->primaryKey()} = $parameter; + $model->find(); + $rows = $model->fetchAll(true); + $wusers_model = &$wusers_ctrl->loadModel($wusers_ctrl->name); + $workflows = ''; + // put the _headline variable in for the viewlist template + foreach ($rows as $key=>$workflow_group) { + $cform = new ControllerForm($wusers_ctrl, $wusers_model); + $form = &$cform->getForm('add_person_form_' . $workflow_group[$pk]); + $form->removeElement('workflow_group_id'); + $form->setDefaults(array('workflow_group_id'=>$workflow_group[$pk])); + $form->addElement('hidden', 'workflow_group_id', $workflow_group[$pk]); + $form->addElement('hidden', '_referer', urlencode($_SERVER['REQUEST_URI'])); + $cform->makeRemoteForm(array('url'=>array('controller'=>'workflow_users', 'action'=>'add_user'), 'loading'=>'workflowManager.addUserLoading(request, ' . $workflow_group[$pk] . ')', 'complete'=>'workflowManager.onAddUser(request, ' . $workflow_group[$pk] . ')')); + if ($form->validate()) { + $fields = $wusers_model->fields(); + if (in_array('cms_created', $fields)) { + $wusers_model->cms_created = $model->now(); + } + if (in_array('cms_modified', $fields)) { + $wusers_model->cms_modified = $model->now(); + } + // set the user id if it's applicable and available + if (in_array('cms_modified_by_user', $fields)) { + $wusers_model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + } + $success = $form->process(array($cform, 'processForm')); + } + $headline = $model->getHeadline(); + if (!empty($headline)) { + if (is_array($headline)) { + $workflow_group['_headline'] = ''; + foreach ($headline as $row) { + $workflow_group['_headline'] .= $workflow_group['_headline']?' - ':''; + $workflow_group['_headline'] .= $workflow_group[$row]; + } + } else { + $workflow_group['_headline'] = $workflow_group[$headline]; + } + } else { + $workflow_group['_headline'] = $workflow_group['cms_headline']; + } + $workflow_group['_users'] = array(); + $wusers_model->reset(); + $wusers_model->workflow_group_id = $workflow_group[$pk]; + if ($wusers_model->find()) { + while ($wusers_model->fetch()) { + $user_model = &$wusers_model->getLink('user_id', 'cms_auth'); + if ($user_model) { + $user = $wusers_model->toArray(); + $user['real_name'] = $user_model->real_name; + $workflow_group['_users'][] = $user; + } + } + } + $workflow_group['asset'] = $this->name; + $workflow_group['add_user_form'] = $form->toHTML(); + $this->set($workflow_group); + $workflows .= $this->render(array('action'=>'workflow_group', 'return'=>true)); + unset($form); + unset($cform); + } + $this->set(array('asset'=>$this->name, 'workflows'=>$workflows)); + $main_content = $this->render(array('return'=>true)); + $sidebar_content = $this->render(array('action'=>'workflow_description', 'return'=>true)); + } + $this->renderLayout('default', $main_content, $sidebar_content); + } + + function delete($parameter) { + if (empty($parameter)) { + $this->redirectTo('viewlist'); + } + // delete the workflow group + $model = &$this->getDefaultModel(); + if (!$model) $this->redirectTo('viewlist'); + $model->get($parameter); + $model->delete($parameter); + // cascade delete the workflow_users records + $workflow_users = &$this->loadModel('workflow_users'); + $workflow_users->workflow_group_id = $parameter; + if ($workflow_users->find()) { + while ($workflow_users->fetch()) { + $workflow_users->delete(); + } + } + if (isset($_GET['_referer']) && $_GET['_referer']) { + header('Location:' . urldecode($_GET['_referer'])); + exit; + } + $this->flash->set('notice', Inflector::humanize($this->name) . ' record deleted.'); + $this->redirectTo('viewlist'); + } +} +?> diff --git a/app/controllers/workflow_users_controller.php b/app/controllers/workflow_users_controller.php new file mode 100644 index 0000000..d4fcbf3 --- /dev/null +++ b/app/controllers/workflow_users_controller.php @@ -0,0 +1,84 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class WorkflowUsersController extends AppController { + function __construct() { + $this->name = 'workflow_users'; + // this whole controller requires login if called directly + $this->login_required = true; + $this->base_view_dir = BASE_DIR; + $this->base_dir = APP_DIR; + // set user level allowed to access the actions with required login + $this->user_level_required = N_USER_ADMIN; + parent::__construct(); + } + + function preGenerateForm() { + parent::preGenerateForm(); + } + + function addUser() { + $model = &$this->getDefaultModel(); + if (isset($_POST['_referer'])) unset($_POST['_referer']); + if ($this->insert()) { + require_once 'vendor/JSON.php'; + $json = new Services_JSON(); + if ($html = $this->loadUser($model->{$model->primaryKey()})) { + $data = array('id'=>$model->{$model->primaryKey()}, 'data'=>$html); + print 'result = ' . $json->encode($data); + } + } + } + + function deleteUser($parameter) { + if (empty($parameter)) { + print 'success=false;'; + return; + } + // load the model layer with info + $model = &$this->getDefaultModel(); + if (!$model) { + $this->render(array('nothing'=>true)); + return; + } + $model->get($parameter); + $model->delete($parameter); + $this->render(array('nothing'=>true)); + } + + function loadUser($parameter) { + $this->auto_render = false; + $model = &$this->loadModel($this->name); + if ($model && $model->get($parameter)) { + $user_model = &$model->getLink('user_id', 'cms_auth'); + if ($user_model) { + $user = $model->toArray(); + $user['real_name'] = $user_model->real_name; + } + $this->set($user); + return $this->render(array('action'=>'workflow_user', 'return'=>true)); + } + $this->render(array('nothing'=>true)); + } + + function listUser($parameter) { + print $this->loadUser($parameter); + } +} +?> diff --git a/app/helpers/audit_trail_helper.php b/app/helpers/audit_trail_helper.php new file mode 100644 index 0000000..e4c3a21 --- /dev/null +++ b/app/helpers/audit_trail_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/cms_asset_info_helper.php b/app/helpers/cms_asset_info_helper.php new file mode 100644 index 0000000..80c71c3 --- /dev/null +++ b/app/helpers/cms_asset_info_helper.php @@ -0,0 +1,53 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAssetInfoHelper{ + + function function_check_asset_model($params) { + $asset = $params['asset']; + $model_check = &NController::factory('cms_asset_info'); + $result = $model_check->doesAssetModelFileExist($asset); + if (!$result) print ' Model File not found '; + unset($model_check); + } + + function function_check_asset_controller($params) { + $asset = $params['asset']; + $controller_check = &NController::factory('cms_asset_info'); + $result = $controller_check->doesAssetControllerFileExist($asset); + if (!$result) print ' Controller File not found '; + unset($controller_check); + } + + function function_check_asset_table($params) { + $asset = $params['asset']; + $table_check = &NController::factory('cms_asset_info'); + $result = $table_check->doesAssetDatabaseTableExist($asset); + if (!$result) print ' Database table not found '; + unset($table_check); + } + + function function_check_asset_template_use($params) { + $asset = $params['asset']; + $container_id = $params['container_id']; + $check = &NController::factory('page_content'); + $result = $check->checkAssetContainerUsage($asset, $container_id); + if ($result > 0) print ' (' . $result . ' uses)'; + } +} +?> \ No newline at end of file diff --git a/app/helpers/cms_asset_template_helper.php b/app/helpers/cms_asset_template_helper.php new file mode 100644 index 0000000..0cbcf46 --- /dev/null +++ b/app/helpers/cms_asset_template_helper.php @@ -0,0 +1,47 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAssetTemplateHelper{ + var $file_warning = ' File not found '; + + function function_check_asset_template($params) { + $filename = $params['filename']; + $asset = $params['asset']; + $template_check = &NController::factory('cms_asset_template'); + $result = $template_check->doesAssetTemplateExist($filename, $asset); + if (!$result) print $this->file_warning; + unset($template_check); + } + + function function_check_template($params) { + $filename = $params['filename']; + $template_check = &NController::factory('page_template'); + $result = $template_check->doesPageTemplateExist($filename); + if (!$result) print $this->file_warning; + unset($template_check); + } + + function function_check_asset_template_use($params) { + $asset = $params['asset']; + $container_id = $params['container_id']; + $check = &NController::factory('page_content'); + $result = $check->checkAssetContainerUsage($asset, $container_id); + if ($result > 0) print ' (' . $result . ' uses)'; + } +} +?> \ No newline at end of file diff --git a/app/helpers/content_helper.php b/app/helpers/content_helper.php new file mode 100644 index 0000000..0d97384 --- /dev/null +++ b/app/helpers/content_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/dashboard_helper.php b/app/helpers/dashboard_helper.php new file mode 100644 index 0000000..96fe578 --- /dev/null +++ b/app/helpers/dashboard_helper.php @@ -0,0 +1,13 @@ +dashboardVersionCheck(); + } + + function function_dashboard_client_content() { + $dashboard = NController::factory('dashboard'); + $dashboard->dashboardClientContent(); + } +} +?> \ No newline at end of file diff --git a/app/helpers/login_helper.php b/app/helpers/login_helper.php new file mode 100644 index 0000000..ff154aa --- /dev/null +++ b/app/helpers/login_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/page_content_helper.php b/app/helpers/page_content_helper.php new file mode 100644 index 0000000..49b9656 --- /dev/null +++ b/app/helpers/page_content_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/page_helper.php b/app/helpers/page_helper.php new file mode 100644 index 0000000..4f57cdf --- /dev/null +++ b/app/helpers/page_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/page_template_containers_helper.php b/app/helpers/page_template_containers_helper.php new file mode 100644 index 0000000..49f9942 --- /dev/null +++ b/app/helpers/page_template_containers_helper.php @@ -0,0 +1,28 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageTemplateContainersHelper{ + function function_check_template($params) { + $filename = $params['filename']; + $template_check = &NController::factory('page_template'); + $result = $template_check->doesPageTemplateExist($filename); + if (!$result) print ' File not found '; + unset($template_check); + } +} +?> \ No newline at end of file diff --git a/app/helpers/page_template_helper.php b/app/helpers/page_template_helper.php new file mode 100644 index 0000000..164f0ed --- /dev/null +++ b/app/helpers/page_template_helper.php @@ -0,0 +1,28 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageTemplateHelper{ + function function_check_template($params) { + $filename = $params['filename']; + $template_check = &NController::factory('page_template'); + $result = $template_check->doesPageTemplateExist($filename); + if (!$result) print ' File not found '; + unset($template_check); + } +} +?> \ No newline at end of file diff --git a/app/helpers/settings_helper.php b/app/helpers/settings_helper.php new file mode 100644 index 0000000..05b64e6 --- /dev/null +++ b/app/helpers/settings_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/site_admin_helper.php b/app/helpers/site_admin_helper.php new file mode 100644 index 0000000..cbfaf41 --- /dev/null +++ b/app/helpers/site_admin_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/helpers/users_helper.php b/app/helpers/users_helper.php new file mode 100644 index 0000000..f735537 --- /dev/null +++ b/app/helpers/users_helper.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/app/models/action_track.php b/app/models/action_track.php new file mode 100644 index 0000000..5193467 --- /dev/null +++ b/app/models/action_track.php @@ -0,0 +1,190 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @since 3.1.14 + * @link http://www.nterchange.com/ + */ +class ActionTrack extends NModel { + var $time_limit = 300; + + function __construct() { + $this->__table = 'action_track'; + $this->_order_by = 'timestamp DESC'; + parent::__construct(); + } + + /** + * cleanURL - Clean up the REQUEST_URI as passed. + * Removes /APP_DIR and the leading slash. + * + * @param string REQUEST_URI + * @return string Cleaned URL + **/ + function cleanURL($url) { + $url = str_replace('/' . APP_DIR, '', $url); + $url = preg_replace('|^/|', '', $url); + return $url; + } + + /** + * setAssetValues - Set the values for the class based on the cleaned up URL. + * + * @param string A cleaned up URL + * @return boolean + **/ + function setAssetValues($url) { + $pieces = explode('/', $url); + $this->asset_name = $pieces[0]; + $this->action = $pieces[1]; + $this->asset_id = $pieces[2]; + return true; + } + + /** + * trackCurrentEdit - Log that a user is editing a record in this->__table. + * + * @param int User id who is editing a record. + * @param string Name of the asset they are editing. + * @param int Id of the asset they are editing + * @return boolean + **/ + function trackCurrentEdit($user_id, $asset_name, $asset_id) { + $this->user_id = $user_id; + $this->asset_name = $asset_name; + $this->action = 'edit'; + $this->asset_id = $asset_id; + $this->timestamp = time(); + if ($this->save()) { + $this->removeOldEdits($user_id, $this->timestamp); + return true; + } else { + return false; + } + } + + /** + * completeCurrentEdit - This is called after a record has been updated + * in app_controller:edit. It removes the record from $this->__table. + * + * @param int User id who is editing a record. + * @param string Name of the asset they are editing. + * @param int Id of the asset they are editing + * @return void + **/ + function completeCurrentEdit($user_id, $asset_name, $asset_id) { + $this->user_id = $user_id; + $this->asset_name = $asset_name; + $this->action = 'edit'; + $this->asset_id = $asset_id; + if ($this->find()) { + while ($this->fetch()) { + $this->delete(); + } + } + } + + /** + * removeOldEdits - This is called after a trackCurrentEdit saves an entry. + * It's to make sure that there's only one entry by that user at any time. + * + * @param int User id who has edited a record. + * @param int Timestamp from time() + * @return void + **/ + function removeOldEdits($user_id, $last_modified) { + $this->reset(); + $this->user_id = $user_id; + if ($this->find()) { + while ($this->fetch()) { + if ($this->timestamp < $last_modified) { + $this->delete(); + } + } + } + } + + /** + * checkAssetEditStatus - This is called from app_controller:edit and returns false if: + * 1. Nobody is editing the record. + * 2. An edit was longer than $this->time_limit. + * It returns true if there somebody is currently editing the same record. + * + * @param string Name of the asset they are editing. + * @param int Id of the asset they are editing + * @return boolean + **/ + function checkAssetEditStatus($asset_name, $asset_id) { + $time_now = time(); + $this->asset_name = $asset_name; + $this->asset_id = $asset_id; + if ($this->find()) { + while ($this->fetch()) { + // Check time limit to see if it's past. + $item = $this->toArray(); + if ($limit = $this->timestampWithinLimit($item['timestamp'])) { + return true; + } else { + return false; + } + } + } else { + return false; + } + } + + /** + * timestampWithinLimit - Check to see whether or not a timestamp is + * within $this->time_limit away from the current time. + * Returns true if it is, false if it's not. + * + * @param int timestamp as returned from time() + * @return boolean + **/ + function timestampWithinLimit($timestamp) { + $time_now = time(); + $time_diff = $time_now - $timestamp; + if ($time_diff < $this->time_limit) { + return true; + } else { + return false; + } + } + + /** + * getActiveEdits - This grabs any currently active edits as long as they're within + * $this->time_limit - used for display by the action_track helper. + * Returns an array of items. + * + * @return array An array of currently active edits. + **/ + function getActiveEdits() { + $items = array(); + if ($this->find()) { + while ($this->fetch()) { + $item = $this->toArray(); + // Check to see if it's within the time_limit; + if ($limit = $this->timestampWithinLimit($item['timestamp'])) { + $items[] = $item; + } + } + } + return $items; + } +} +?> diff --git a/app/models/cms_asset_info.php b/app/models/cms_asset_info.php new file mode 100644 index 0000000..e7b1584 --- /dev/null +++ b/app/models/cms_asset_info.php @@ -0,0 +1,29 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAssetInfo extends NModel { + function __construct() { + $this->__table = 'cms_asset_info'; + $this->_order_by = 'cms_asset_info.asset_name'; + $this->setHeadline('asset_name'); + parent::__construct(); + } + +} +?> \ No newline at end of file diff --git a/app/models/cms_asset_template.php b/app/models/cms_asset_template.php new file mode 100644 index 0000000..be5ab43 --- /dev/null +++ b/app/models/cms_asset_template.php @@ -0,0 +1,29 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAssetTemplate extends NModel { + function __construct() { + $this->__table = 'cms_asset_template'; + $this->_order_by = $this->__table . '.asset'; + $this->setHeadline('asset'); + $this->form_elements['page_template_container_id'] = array('foreignkey', 'page_template_container_id', 'Container:', array('model'=>'page_template_containers', 'headline'=>'container_name', 'addEmptyOption'=>false)); + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/cms_audit_trail.php b/app/models/cms_audit_trail.php new file mode 100644 index 0000000..9777402 --- /dev/null +++ b/app/models/cms_audit_trail.php @@ -0,0 +1,70 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAuditTrail extends NModel { + function __construct() { + $this->__table = 'cms_audit_trail'; + $this->name = 'cms_audit_trail'; + $this->_order_by = 'cms_created ASC'; + // This is the fake user we use when the website removes timed content from the site. + // It's to keep audit trail working. + $this->website_user_id = 99999; + $this->website_user_name = 'Website Robot'; + $this->website_user_email = 'website@' . $_SERVER['SERVER_NAME']; + parent::__construct(); + } + + /** + * insert_audit_trail - This is only for timed_remove so that we don't + * lose the audit_trail. + * Refactor: Duplication of audit_trail_controller->insert(); + * + * @param array Required params - asset, asset_id, action_taken + * @return void + **/ + function insert_audit_trail($params=array()) { + if (empty($params)) return false; + $required_params = array('asset', 'asset_id', 'action_taken'); + foreach ($required_params as $param) { + if (!isset($params[$param])) return false; + } + $model = &NModel::factory($this->name); + // apply fields in the model + $fields = $model->fields(); + foreach ($fields as $field) { + $model->$field = isset($params[$field])?$params[$field]:null; + } + $model->user_id = $this->website_user_id; + $model->ip = NServer::env('REMOTE_ADDR'); + if (in_array('cms_created', $fields)) { + $model->cms_created = $model->now(); + } + if (in_array('cms_modified', $fields)) { + $model->cms_modified = $model->now(); + } + // set the user id if it's applicable and available + if (in_array('cms_modified_by_user', $fields)) { + $model->cms_modified_by_user = $this->website_user_id; + } + $model->insert(); + } + +} +?> \ No newline at end of file diff --git a/app/models/cms_auth.php b/app/models/cms_auth.php new file mode 100644 index 0000000..97d47cf --- /dev/null +++ b/app/models/cms_auth.php @@ -0,0 +1,173 @@ + + * @author Darron Froese + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsAuth extends NModel { + function __construct() { + $this->__table = 'cms_auth'; + $this->_order_by = 'real_name'; + parent::__construct(); + // form stuff + $this->form_header = 'real_name'; + $this->display_fields = array('real_name', 'username', 'user_level', 'status'); + $this->form_required_fields[] = 'real_name'; + $this->form_required_fields[] = 'email'; + $this->form_required_fields[] = 'username'; + $this->form_rules[] = array('email', 'The email does not appear to be the correct format', 'email'); + $this->form_rules[] = array('username', 'This is not a unique username', 'callback', array(&$this, 'uniqueUsername')); + // secure password rules, 8 characters long, upper and lower case characters + // $this->form_rules[] = array('password', 'The password must be at least 8 characters long and contain upper and lower case characters and a number.', 'minlength', 8, 'client'); + // $this->form_rules[] = array('password', 'The password must be at least 8 characters long and contain upper and lower case characters and a number.', 'regex', '/[A-Z]/', 'client'); + // $this->form_rules[] = array('password', 'The password must be at least 8 characters long and contain upper and lower case characters and a number.', 'regex', '/[a-z]/', 'client'); + // $this->form_rules[] = array('password', 'The password must be at least 8 characters long and contain upper and lower case characters and a number.', 'regex', '/[0-9]/', 'client'); + // password field + $this->form_elements['password'] = array('password'); + // hide the status field for now + $this->form_elements['status'] = array('hidden', 'status'); + // Ignore the feed_token field for now + $this->form_ignore_fields[] = 'feed_token'; + // Ignore the confirmation_token field for now + $this->form_ignore_fields[] = 'confirmation_token'; + // user level - a non-root can't set someone else to be root + $user_lvl = array(N_USER_NORIGHTS=>'non-privileged', N_USER_EDITOR=>'user', N_USER_ADMIN=>'admin'); + $this->form_elements['user_level'] = array('select', 'user_level', 'User Level', $user_lvl); + $this->setHeadline('real_name'); + } + + /** + * uniqueUsername - Make sure that the username is unique. + * + * @param string The username to check. + * @return boolean + **/ + function uniqueUsername($value) { + $id = $this->{$this->primaryKey()}; + $model = &NModel::factory($this->__table); + if ($model) { + $conditions = $id?$model->primaryKey() . '!=' . $id:''; + $model->username = $value; + if ($model->find(array('conditions'=>$conditions))) { + unset($model); + return false; + } + } + unset($model); + return true; + } + + /** + * getFeedToken - Returns the feed token for an id. + * + * @param int User_id + * @return string The feed token for that user. + **/ + function getFeedToken($id) { + $this->id = $id; + if ($this->find()) { + while ($this->fetch()) { + return $this->feed_token; + } + } + } + + /** + * resetPassword - Reset the users' password and email it to them. + * + * @param string An email address. + * @return boolean + * @todo Audit trail this method. + **/ + function resetPassword($email) { + // Make sure to clear out the model - after searching a few times already. + $this->reset(); + $this->email = $email; + if ($this->find()) { + while ($this->fetch()) { + $password = $this->_createPassword(); + $this->password = md5($password); + $this->save(); + $users = NController::factory('users'); + $users->passwordEmail($this->toArray(), $password); + return true; + } + } else { + return false; + } + } + + /** + * _createPassword - Create a password using PEAR's Text_Password generator. + * + * @return string The new password. + **/ + function _createPassword() { + include_once 'Text/Password.php'; + $password = Text_Password::create(rand(8, 10), 'unpronounceable'); + return $password; + } + + /** + * setConfirmationToken - Generate and set a confirmation token for a particular user. + * Returns true if successful, false if not. + * + * @param string Email address for that user. + * @return boolean + **/ + function setConfirmationToken($email) { + // Generate the token and put it into the database. + $random = rand(1,1000000) . time() . $_SERVER['REMOTE_ADDR'] . rand(1,1000000); + $confirmation_token = md5($random); + $this->email = $email; + if ($this->find()) { + while ($this->fetch()) { + $this->confirmation_token = $confirmation_token; + if ($this->save()) { + return true; + } else { + return false; + } + } + } + } + + /** + * getConfirmationToken - Gets a confirmation token from the database. + * Returns false if it's null or if there's no match. + * + * @param string Email address for that user. + * @return string The confirmation token. + **/ + function getConfirmationToken($email) { + $this->email = $email; + if ($this->find()) { + while ($this->fetch()) { + if (!is_null($this->confirmation_token)) { + return $this->confirmation_token; + } else { + return false; + } + } + } else { + return false; + } + } + + +} +?> \ No newline at end of file diff --git a/app/models/cms_drafts.php b/app/models/cms_drafts.php new file mode 100644 index 0000000..356f336 --- /dev/null +++ b/app/models/cms_drafts.php @@ -0,0 +1,26 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsDrafts extends NModel { + function __construct() { + $this->__table = 'cms_drafts'; + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/cms_nterchange_versions.php b/app/models/cms_nterchange_versions.php new file mode 100644 index 0000000..cce22b2 --- /dev/null +++ b/app/models/cms_nterchange_versions.php @@ -0,0 +1,27 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsNterchangeVersions extends NModel { + function __construct() { + $this->__table = 'cms_nterchange_versions'; + $this->_order_by = 'cms_nterchange_versions.cms_created desc'; + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/cms_settings.php b/app/models/cms_settings.php new file mode 100644 index 0000000..e78405a --- /dev/null +++ b/app/models/cms_settings.php @@ -0,0 +1,90 @@ +SETTINGS_EDITOR_DEFAULT); + +/** + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category CMS Settings Model + * @author Tim Glen + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CmsSettings extends NModel { + function __construct() { + $this->__table = 'cms_settings'; + $this->setHeadline('setting_var'); + $this->_order_by = 'cms_settings.setting'; + $this->form_elements['setting'] = array('select', 'setting', 'Setting', array(SETTINGS_EDITOR=>'WYSIWYG Editor')); + $this->form_ignore_fields[] = 'user_id'; + parent::__construct(); + } + + /** + * settingToText - Returns English description of setting. + * + * @param int Setting define id + * @return string English description of that string + **/ + function settingToText($setting) { + $ret = ''; + switch($setting) { + case SETTINGS_EDITOR: + $ret = 'WYSIWYG Editor'; + break; + } + return $ret; + } + + function fetch() { + $ret = parent::fetch(); + if ($ret) { + $this->setting_var = $this->settingToText($this->setting); + } + return $ret; + } + + function toArray() { + $ret = parent::toArray(); + if (is_array($ret) && count($ret) && isset($this->setting_var)) { + $ret['setting_var'] = $this->setting_var; + } + return $ret; + } + + /** + * getSetting - Get a user's setting from the database - or use the defaults. + * + * @param int The id of the particular setting. + * @return boolean + **/ + function getSetting($setting) { + $auth = new NAuth(); + $this->user_id = $auth->currentUserID(); + $this->setting = $setting; + if ($this->find(null, true)) { + $ret = (bool) $this->value; + } else { + $user_settings = $GLOBALS['USER_SETTINGS']; + $ret = isset($user_settings[$setting])?$user_settings[$setting]:true; + } + $this->reset(); + return $ret; + } +} +?> diff --git a/app/models/code_caller.php b/app/models/code_caller.php new file mode 100644 index 0000000..5562076 --- /dev/null +++ b/app/models/code_caller.php @@ -0,0 +1,28 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class CodeCaller extends NModel { + function __construct() { + $this->__table = 'code_caller'; + $this->form_required_fields[] = 'content'; + $this->_order_by = 'cms_headline'; + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/page.php b/app/models/page.php new file mode 100644 index 0000000..90a583b --- /dev/null +++ b/app/models/page.php @@ -0,0 +1,140 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class Page extends ModelTree { + var $_order_by = 'page.sort_order, page.id'; + + function __construct() { + $this->__table = 'page'; + parent::__construct(); + $this->setHeadline('title'); + // form & validation + $this->form_header = 'title'; + $this->form_field_labels['filename'] = 'Filename
    (nonfiction use)'; + // always ignore these as they are updated programatically + $this->form_ignore_fields = array('path', 'sort_order', 'static_content'); + if (!defined('SITE_DISCLAIMER') || !SITE_DISCLAIMER || !defined('SITE_DISCLAIMER_ID') || !SITE_DISCLAIMER_ID) { + $this->form_ignore_fields[] = 'disclaimer_required'; + $this->form_ignore_fields[] = 'disclaimer_recursive'; + } + // always require the title + $this->form_required_fields = array('title'); + // cache lifetime options + $this->form_elements['cache_lifetime'] = array('select', 'cache_lifetime', 'Cache Lifetime', array(0=>'no caching', 5*60=>'5 minutes', 15*60=>'15 minutes', 60*60=>'1 hour', 60*60*8=>'8 hours', 60*60*24=>'1 day', 60*60*24*7=>'1 week', '-1'=>'indefinite')); + // cache lifetime options + $this->form_elements['client_cache_lifetime'] = array('select', 'client_cache_lifetime', 'Client Cache Lifetime', array(0=>'no caching', 5*60=>'5 minutes', 15*60=>'15 minutes', 60*60=>'1 hour', 60*60*8=>'8 hours', 60*60*24=>'1 day', 60*60*24*7=>'1 week', '-1'=>'indefinite')); + + // this is a foreign key + $this->form_elements['page_template_id'] = array('foreignkey', 'page_template_id', 'Template', array('model'=>'page_template', 'headline'=>'template_name')); + // Ignore printable - not being used in nterchange 3 at all. + $this->form_ignore_fields[] = 'printable'; + + // Ignore permissions field - not being used in nterchange 3 at the moment. + $this->form_ignore_fields[] = 'permissions_id'; + + // check for workflow being used + if (SITE_WORKFLOW) { + $this->form_elements['workflow_group_id'] = array('foreignkey', 'workflow_group_id', 'Workflow Group', array('model'=>'workflow_group', 'headline'=>'workflow_title', 'addEmptyOption'=>true)); + $this->form_field_labels['workflow_recursive'] = 'Cascade Workflow'; + } else { + $this->form_ignore_fields[] = 'workflow_group_id'; + $this->form_ignore_fields[] = 'workflow_recursive'; + } + if (!SECURE_SITE) { + $this->form_ignore_fields[] = 'secure_page'; + } + $this->form_rules[] = array('parent_id', 'You cannot make a page a child of itself.', 'callback', array(&$this, 'checkChildOfItself')); + + // cache lifetime should be indefinite by default + $this->form_field_defaults['cache_lifetime'] = '-1'; + $this->form_field_defaults['client_cache_lifetime'] = '3600'; + $this->form_field_defaults['visible'] = 1; + $this->form_field_defaults['active'] = 1; + } + + /** + * checkChildOfItself - A page cannot be a child of itself. That would be + * illogical and cause that page to disappear. Returns true if OK and false + * if you're setting something to be a child of itself. + * + * @param int The page_id of a page. + * @return boolean + **/ + function checkChildOfItself($parent_id) { + $parent_id = (int) $parent_id; + $pk = $this->primaryKey(); + $page = &NController::singleton('page'); + $page_id = (int) $page->getParam($pk); + if (!$page_id) { + return true; + } + if ($page_id == $parent_id) { + return false; + } + $all_children = $this->getAllChildren($page_id, false, false); + foreach ($all_children as $child) { + if ($parent_id == $child[$pk]) { + return false; + } + } + return true; + } + + function afterCreate() { + // Delete Smarty Cache + $this->deleteSmartyCache(); + } + + function afterUpdate() { + // Delete Smarty Cache + $this->deleteSmartyCache(); + } + + function afterDelete($page_id) { + // After a page is deleted, make sure to remove all linked + // content from the page_content table. Cleans things up and + // helps with some possible workflow trauma. + $page_content = NModel::factory('page_content'); + $page_content->deleteOrphanedPageContent($page_id); + // Delete Smarty Cache + $this->deleteSmartyCache(); + } + + /** + * deleteSmartyCache - Delete the entire cache when you make a page change. + * If you're including the navigation in the page as ul/li's - this keeps the + * navigation always consistent. + * + * @return void + **/ + function deleteSmartyCache() { + // Only delete the entire cache if the NAV_IN_PAGE is true and we're in production. + if (defined('NAV_IN_PAGE') && NAV_IN_PAGE && (ENVIRONMENT == 'production') && !isset($this->smarty_cache_cleared)) { + NDebug::debug('We are clearing the smarty caches because of a page edit.' , N_DEBUGTYPE_INFO); + $view = &NView::singleton($this); + $view->clear_all_cache(); + $site_admin = NController::factory('site_admin'); + $site_admin->rmDirFiles(CACHE_DIR . '/smarty_cache'); + $site_admin->rmDirFiles(CACHE_DIR . '/templates_c'); + $this->smarty_cache_cleared = true; + } + } +} +?> \ No newline at end of file diff --git a/app/models/page_content.php b/app/models/page_content.php new file mode 100644 index 0000000..204a83c --- /dev/null +++ b/app/models/page_content.php @@ -0,0 +1,256 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageContent extends NModel { + function __construct() { + $this->__table = 'page_content'; + $this->_order_by = 'page_content.content_order, page_content.id'; + + $this->form_field_options['timed_start'] = array('addEmptyOption'=>true); + $this->form_field_options['timed_end'] = array('addEmptyOption'=>true); + + $this->form_elements['col_xs'] = array('select', 'col_xs', 'Width (xs)', $this->grid_options('col_xs')); + $this->form_elements['col_sm'] = array('select', 'col_sm', 'Width (sm)', $this->grid_options('col')); + $this->form_elements['col_md'] = array('select', 'col_md', 'Width (md)', $this->grid_options('col')); + $this->form_elements['col_lg'] = array('select', 'col_lg', 'Width (lg)', $this->grid_options('col')); + + $this->form_elements['row_xs'] = array('select', 'row_xs', 'Height (xs)', $this->grid_options('row_xs')); + $this->form_elements['row_sm'] = array('select', 'row_sm', 'Height (sm)', $this->grid_options('row')); + $this->form_elements['row_md'] = array('select', 'row_md', 'Height (md)', $this->grid_options('row')); + $this->form_elements['row_lg'] = array('select', 'row_lg', 'Height (lg)', $this->grid_options('row')); + + $this->form_elements['offset_col_xs'] = array('select', 'offset_col_xs', 'Offset Width (xs)', $this->grid_options('offset_col_xs')); + $this->form_elements['offset_col_sm'] = array('select', 'offset_col_sm', 'Offset Width (sm)', $this->grid_options('offset_col')); + $this->form_elements['offset_col_md'] = array('select', 'offset_col_md', 'Offset Width (md)', $this->grid_options('offset_col')); + $this->form_elements['offset_col_lg'] = array('select', 'offset_col_lg', 'Offset Width (lg)', $this->grid_options('offset_col')); + + $this->form_elements['offset_row_xs'] = array('select', 'offset_row_xs', 'Offset Height (xs)', $this->grid_options('offset_row_xs')); + $this->form_elements['offset_row_sm'] = array('select', 'offset_row_sm', 'Offset Height (sm)', $this->grid_options('offset_row')); + $this->form_elements['offset_row_md'] = array('select', 'offset_row_md', 'Offset Height (md)', $this->grid_options('offset_row')); + $this->form_elements['offset_row_lg'] = array('select', 'offset_row_lg', 'Offset Height (lg)', $this->grid_options('offset_row')); + + $this->form_elements['pull_xs'] = array('select', 'pull_xs', 'Pull (xs)', $this->grid_options('pull_xs')); + $this->form_elements['pull_sm'] = array('select', 'pull_sm', 'Pull (sm)', $this->grid_options('pull')); + $this->form_elements['pull_md'] = array('select', 'pull_md', 'Pull (md)', $this->grid_options('pull')); + $this->form_elements['pull_lg'] = array('select', 'pull_lg', 'Pull (lg)', $this->grid_options('pull')); + + $this->form_elements['gutter_xs'] = array('text', 'gutter_xs', 'Gutter (xs)'); + $this->form_elements['gutter_sm'] = array('text', 'gutter_sm', 'Gutter (sm)'); + $this->form_elements['gutter_md'] = array('text', 'gutter_md', 'Gutter (md)'); + $this->form_elements['gutter_lg'] = array('text', 'gutter_lg', 'Gutter (lg)'); + + parent::__construct(); + } + + function getContainerContent($page_id, $container_id, $admin=false, $page_content_id=null) { + $options = array('conditions'=>"page_id=$page_id AND page_template_container_id=$container_id"); + if ($admin == false) { + $options['conditions'] .= ' AND cms_workflow=0'; + } + if ($page_content_id) { + $options['conditions'] .= " AND id=$page_content_id"; + } + return $this->find($options); + } + + function isWorkflowContent($asset, $asset_id) { + if (SITE_WORKFLOW) { + $this->reset(); + $page_model = &NModel::singleton('page'); + $page_model->reset(); + $join = 'INNER JOIN ' . $page_model->tableName() . ' ON '; + $join .= $this->tableName() . '.page_id=' . $page_model->tableName() . '.' . $page_model->primaryKey(); + $this->content_asset = $asset; + $this->content_asset_id = $asset_id; + if ($this->find(array('join'=>$join, 'conditions'=>$page_model->tableName() . '.workflow_group_id != 0'))) { + return true; + } + } + return false; + } + + function &getActivePageContent($asset, $asset_id) { + if (!$asset || !$asset_id) { + $ret = false; + return $ret; + } + $page = &NModel::singleton('page'); + $this->reset(); + $this->content_asset = (string) $asset; + $this->content_asset_id = (int) $asset_id; + $page_contents = false; + if ($this->find()) { + $page_contents = array(); + while ($this->fetch()) { + $page->reset(); + // Only get active pages. + $page->cms_deleted = 0; + if (!$page->get($this->page_id)) { + continue; + } + $page_contents[] = clone($this); + } + } + return $page_contents; + } + + /** + * deleteOrphanedPageContent - Remove all content from the page_content table for + * a particular page_id. This is called from the page model when a page is deleted. + * + * @param int The id of a page that has been deleted. + * @return void + **/ + function deleteOrphanedPageContent($page_id) { + $this->page_id = $page_id; + if ($this->find()) { + while ($this->fetch()) { + $this->delete(); + } + } + } + + function to_percent($col) { + $percent = round(($col/12)*100); + return "{$percent}%"; + } + + function grid_options($name) { + $col = array( + 'inherit' => 'Inherit', + '12' => '12 cols', + '11' => '11 cols', + '10' => '10 cols', + '9' => '9 cols', + '8' => '8 cols', + '7' => '7 cols', + '6' => '6 cols', + '5' => '5 cols', + '4' => '4 cols', + '3' => '3 cols', + '2' => '2 cols', + '1' => '1 col', + 'auto' => 'Auto'); + $col_xs = array( + '12' => '12 cols', + '11' => '11 cols', + '10' => '10 cols', + '9' => '9 cols', + '8' => '8 cols', + '7' => '7 cols', + '6' => '6 cols', + '5' => '5 cols', + '4' => '4 cols', + '3' => '3 cols', + '2' => '2 cols', + '1' => '1 col', + 'auto' => 'Auto'); + + $row = array('inherit'=>'Inherit', 'auto'=>'Auto', + '1'=> '1 row', '2'=> '2 rows', '3'=> '3 rows', '4'=> '4 rows', '5'=> '5 rows', '6'=> '6 rows', '7'=> '7 rows', '8'=> '8 rows', '9'=> '9 rows', '10'=>'10 rows', + '11'=>'11 rows', '12'=>'12 rows', '13'=>'13 rows', '14'=>'14 rows', '15'=>'15 rows', '16'=>'16 rows', '17'=>'17 rows', '18'=>'18 rows', '19'=>'19 rows', '20'=>'20 rows', + '21'=>'21 rows', '22'=>'22 rows', '23'=>'23 rows', '24'=>'24 rows', '25'=>'25 rows', '26'=>'26 rows', '27'=>'27 rows', '28'=>'28 rows', '29'=>'29 rows', '30'=>'30 rows', + '31'=>'31 rows', '32'=>'32 rows', '33'=>'33 rows', '34'=>'34 rows', '35'=>'35 rows', '36'=>'36 rows', '37'=>'37 rows', '38'=>'38 rows', '39'=>'39 rows', '40'=>'40 rows', + '41'=>'41 rows', '42'=>'42 rows', '43'=>'43 rows', '44'=>'44 rows', '45'=>'45 rows', '46'=>'46 rows', '47'=>'47 rows', '48'=>'48 rows', '49'=>'49 rows', '50'=>'50 rows'); + $row_xs = array('auto'=>'Auto', + '1'=> '1 row', '2'=> '2 rows', '3'=> '3 rows', '4'=> '4 rows', '5'=> '5 rows', '6'=> '6 rows', '7'=> '7 rows', '8'=> '8 rows', '9'=> '9 rows', '10'=>'10 rows', + '11'=>'11 rows', '12'=>'12 rows', '13'=>'13 rows', '14'=>'14 rows', '15'=>'15 rows', '16'=>'16 rows', '17'=>'17 rows', '18'=>'18 rows', '19'=>'19 rows', '20'=>'20 rows', + '21'=>'21 rows', '22'=>'22 rows', '23'=>'23 rows', '24'=>'24 rows', '25'=>'25 rows', '26'=>'26 rows', '27'=>'27 rows', '28'=>'28 rows', '29'=>'29 rows', '30'=>'30 rows', + '31'=>'31 rows', '32'=>'32 rows', '33'=>'33 rows', '34'=>'34 rows', '35'=>'35 rows', '36'=>'36 rows', '37'=>'37 rows', '38'=>'38 rows', '39'=>'39 rows', '40'=>'40 rows', + '41'=>'41 rows', '42'=>'42 rows', '43'=>'43 rows', '44'=>'44 rows', '45'=>'45 rows', '46'=>'46 rows', '47'=>'47 rows', '48'=>'48 rows', '49'=>'49 rows', '50'=>'50 rows'); + + $offset_col = array( + 'inherit' => 'Inherit', + '0' => '0 cols', + '1' => '1 col', + '2' => '2 cols', + '3' => '3 cols', + '4' => '4 cols', + '5' => '5 cols', + '6' => '6 cols', + '7' => '7 cols', + '8' => '8 cols', + '9' => '9 cols', + '10' => '10 cols', + '11' => '11 cols', + '12' => '12 cols'); + $offset_col_xs = array( + '0' => '0 cols', + '1' => '1 col', + '2' => '2 cols', + '3' => '3 cols', + '4' => '4 cols', + '5' => '5 cols', + '6' => '6 cols', + '7' => '7 cols', + '8' => '8 cols', + '9' => '9 cols', + '10' => '10 cols', + '11' => '11 cols', + '12' => '12 cols'); + + $offset_row = array('inherit'=>'Inherit', '0'=>'0 rows', + '-25'=>'-25 rows', '-24'=>'-24 rows', '-23'=>'-23 rows', '-22'=>'-22 rows', '-21'=>'-20 rows', + '-19'=>'-19 rows', '-18'=>'-18 rows', '-17'=>'-17 rows', '-16'=>'-16 rows', '-16'=>'-16 rows', + '-15'=>'-15 rows', '-14'=>'-14 rows', '-13'=>'-13 rows', '-12'=>'-12 rows', '-11'=>'-11 rows', + '-10'=>'-10 rows', '-9'=> '-9 rows', '-8'=> '-8 rows', '-7'=> '-7 rows', '-6'=> '-6 rows', + '-5'=> '-5 rows', '-4'=> '-4 rows', '-3'=> '-3 rows', '-2'=> '-2 rows', '-1'=> '-1 row', + '1'=> '1 row', '2'=> '2 rows', '3'=> '3 rows', '4'=> '4 rows', '5'=> '5 rows', + '6'=> '6 rows', '7'=> '7 rows', '8'=> '8 rows', '9'=> '9 rows', '10'=> '10 rows', + '11'=> '11 rows', '12'=> '12 rows', '13'=> '13 rows', '14'=> '14 rows', '15'=> '15 rows', + '16'=> '16 rows', '17'=> '17 rows', '18'=> '18 rows', '19'=> '19 rows', '20'=> '20 rows', + '21'=> '21 rows', '22'=> '22 rows', '23'=> '23 rows', '24'=> '24 rows', '25'=> '25 rows'); + + $offset_row_xs = array('0'=>'0 rows', + '-25'=>'-25 rows', '-24'=>'-24 rows', '-23'=>'-23 rows', '-22'=>'-22 rows', '-21'=>'-20 rows', + '-19'=>'-19 rows', '-18'=>'-18 rows', '-17'=>'-17 rows', '-16'=>'-16 rows', '-16'=>'-16 rows', + '-15'=>'-15 rows', '-14'=>'-14 rows', '-13'=>'-13 rows', '-12'=>'-12 rows', '-11'=>'-11 rows', + '-10'=>'-10 rows', '-9'=> '-9 rows', '-8'=> '-8 rows', '-7'=> '-7 rows', '-6'=> '-6 rows', + '-5'=> '-5 rows', '-4'=> '-4 rows', '-3'=> '-3 rows', '-2'=> '-2 rows', '-1'=> '-1 row', + '1'=> '1 row', '2'=> '2 rows', '3'=> '3 rows', '4'=> '4 rows', '5'=> '5 rows', + '6'=> '6 rows', '7'=> '7 rows', '8'=> '8 rows', '9'=> '9 rows', '10'=> '10 rows', + '11'=> '11 rows', '12'=> '12 rows', '13'=> '13 rows', '14'=> '14 rows', '15'=> '15 rows', + '16'=> '16 rows', '17'=> '17 rows', '18'=> '18 rows', '19'=> '19 rows', '20'=> '20 rows', + '21'=> '21 rows', '22'=> '22 rows', '23'=> '23 rows', '24'=> '24 rows', '25'=> '25 rows'); + + $pull = array( + 'inherit' => 'Inherit', + 'none' => 'None', + 'right' => 'Right', + 'left' => 'Left'); + $pull_xs = array( + 'none' => 'None', + 'right' => 'Right', + 'left' => 'Left'); + + switch ($name) { + case 'col': return $col; + case 'col_xs': return $col_xs; + case 'row': return $row; + case 'row_xs': return $row_xs; + case 'offset_col': return $offset_col; + case 'offset_col_xs': return $offset_col_xs; + case 'offset_row': return $offset_row; + case 'offset_row_xs': return $offset_row_xs; + case 'pull': return $pull; + case 'pull_xs': return $pull_xs; + } + } +} +?> diff --git a/app/models/page_template.php b/app/models/page_template.php new file mode 100644 index 0000000..11b2675 --- /dev/null +++ b/app/models/page_template.php @@ -0,0 +1,27 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageTemplate extends NModel { + function __construct() { + $this->__table = 'page_template'; + $this->setHeadline('template_name'); + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/page_template_containers.php b/app/models/page_template_containers.php new file mode 100644 index 0000000..5a459ad --- /dev/null +++ b/app/models/page_template_containers.php @@ -0,0 +1,28 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class PageTemplateContainers extends NModel { + function __construct() { + $this->__table = 'page_template_containers'; + $this->setHeadline('container_name'); + $this->form_elements['page_template_id'] = array('foreignkey', 'page_template_id', 'Page Template:', array('model'=>'page_template', 'headline'=>'template_name', 'addEmptyOption'=>false)); + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/permissions.php b/app/models/permissions.php new file mode 100644 index 0000000..e45ec60 --- /dev/null +++ b/app/models/permissions.php @@ -0,0 +1,26 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class Permissions extends NModel { + function __construct() { + $this->__table = 'permissions'; + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/redirect.php b/app/models/redirect.php new file mode 100644 index 0000000..00758e7 --- /dev/null +++ b/app/models/redirect.php @@ -0,0 +1,47 @@ + + * @author Darron Froese + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class Redirect extends NModel { + function __construct() { + $this->__table = 'redirect'; + $this->form_ignore_fields[] = 'count'; + $this->not_connectable = true; + $this->form_rules[] = array('url', 'This is not a unique url', 'callback', array(&$this, 'validate_url')); + parent::__construct(); + } + + /** + * validate_url - Each URL must be unique. Having duplicates is a problem. + * This is called because it's set in $this->form_rules. + * + * @param string The value of the $url field. + * @return boolean + **/ + function validate_url($value) { + $this->url = $value; + if ($this->find()) { + return false; + } else { + return true; + } + } + +} +?> \ No newline at end of file diff --git a/app/models/test_sample.php b/app/models/test_sample.php new file mode 100644 index 0000000..f74080d --- /dev/null +++ b/app/models/test_sample.php @@ -0,0 +1,29 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class TestSample extends NModel { + function __construct() { + $this->__table = 'test_sample'; + parent::__construct(); + } + + function test_method(){ + return $this->cms_headline.' '.$this->the_text; + } +} diff --git a/app/models/workflow.php b/app/models/workflow.php new file mode 100644 index 0000000..0e5d546 --- /dev/null +++ b/app/models/workflow.php @@ -0,0 +1,31 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class Workflow extends NModel { + var $_order_by = 'workflow.id DESC'; + function __construct() { + $this->__table = 'workflow'; + $this->form_ignore_fields = array('page_id', 'page_content_id', 'workflow_group_id', 'asset', 'asset_id', 'action', 'draft', 'submitted', 'parent_workflow', 'completed'); + $this->form_elements['approved'] = array('select', 'workflow_approve', 'Approval', array('1'=>'Approve', '0'=>'Decline')); + $this->form_field_options['timed_start'] = array('addEmptyOption'=>true); + $this->form_field_options['timed_end'] = array('addEmptyOption'=>true); + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/workflow_group.php b/app/models/workflow_group.php new file mode 100644 index 0000000..b5173f9 --- /dev/null +++ b/app/models/workflow_group.php @@ -0,0 +1,28 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class WorkflowGroup extends NModel { + function __construct() { + $this->__table = 'workflow_group'; + $this->setHeadline('workflow_title'); + $this->_order_by = 'id'; + parent::__construct(); + } +} +?> \ No newline at end of file diff --git a/app/models/workflow_users.php b/app/models/workflow_users.php new file mode 100644 index 0000000..26c9d82 --- /dev/null +++ b/app/models/workflow_users.php @@ -0,0 +1,33 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + */ +class WorkflowUsers extends NModel { + function __construct() { + $this->__table = 'workflow_users'; + $this->_order_by = 'workflow_group_id, cms_created DESC'; + parent::__construct(); + // load the workgroup constants + include_once BASE_DIR . '/app/controllers/workflow_group_controller.php'; + $this->form_elements['workflow_group_id'] = array('foreignkey', 'workflow_group_id', 'Workflow Group', array('model'=>'workflow_group', 'headline'=>'workflow_title', 'addEmptyOption'=>true)); + $this->form_elements['user_id'] = array('foreignkey', 'user_id', 'User', array('model'=>'cms_auth', 'headline'=>'real_name', 'addEmptyOption'=>true)); + $this->form_elements['role'] = array('select', 'role', 'Role', array(''=>'Select...', WORKFLOW_ROLE_AUTHOR=>'Author', WORKFLOW_ROLE_EDITOR=>'Editor', WORKFLOW_ROLE_APPROVER=>'Approver')); + $this->form_required_fields = array('workflow_group_id', 'user_id', 'role'); + } +} +?> \ No newline at end of file diff --git a/app/views/audit_trail/audit_trail_record.html b/app/views/audit_trail/audit_trail_record.html new file mode 100755 index 0000000..ab6ab96 --- /dev/null +++ b/app/views/audit_trail/audit_trail_record.html @@ -0,0 +1,33 @@ +
    +
    + {$action_taken} +
    + {if $asset} +
    + {$asset_name}: + {if $asset.cms_deleted} + {$asset._headline} (deleted) + {else} + {link_to href="controller:`$asset_type`;action:show;id:`$asset.id`;" text=$asset._headline rel="blank"} + {/if} +
    + {/if} + {if $page} +
    + Page: + {if $page.cms_deleted == 0} + {link_to href="controller:page;action:surftoedit;id:`$page.id`;" text=$page.title rel="blank"} + {else} + {$page.title} (deleted) + {/if} +
    + {/if} + {if $workflow_group} +
    + Workflow Group: {$workflow_group.workflow_title} +
    + {/if} +
    + {$user.real_name} @ {$created|date_format:'%I:%M:%S %p on %F'} from the IP address: {$ip} +
    +
    diff --git a/app/views/audit_trail/page.html b/app/views/audit_trail/page.html new file mode 100644 index 0000000..e30cc6e --- /dev/null +++ b/app/views/audit_trail/page.html @@ -0,0 +1,3 @@ +{$rss_feed} + +{$audit_trail} \ No newline at end of file diff --git a/app/views/audit_trail/page_audit_trail_record.html b/app/views/audit_trail/page_audit_trail_record.html new file mode 100644 index 0000000..ab6ab96 --- /dev/null +++ b/app/views/audit_trail/page_audit_trail_record.html @@ -0,0 +1,33 @@ +
    +
    + {$action_taken} +
    + {if $asset} +
    + {$asset_name}: + {if $asset.cms_deleted} + {$asset._headline} (deleted) + {else} + {link_to href="controller:`$asset_type`;action:show;id:`$asset.id`;" text=$asset._headline rel="blank"} + {/if} +
    + {/if} + {if $page} +
    + Page: + {if $page.cms_deleted == 0} + {link_to href="controller:page;action:surftoedit;id:`$page.id`;" text=$page.title rel="blank"} + {else} + {$page.title} (deleted) + {/if} +
    + {/if} + {if $workflow_group} +
    + Workflow Group: {$workflow_group.workflow_title} +
    + {/if} +
    + {$user.real_name} @ {$created|date_format:'%I:%M:%S %p on %F'} from the IP address: {$ip} +
    +
    diff --git a/app/views/audit_trail/viewlist.html b/app/views/audit_trail/viewlist.html new file mode 100644 index 0000000..6fd26dc --- /dev/null +++ b/app/views/audit_trail/viewlist.html @@ -0,0 +1,13 @@ + +

    Your search on {$date|date_format:'%b %e, %Y'} found {$result_count} matches.

    +{$rss_feed} +{$audit_trail} diff --git a/app/views/ck_upload/image_browse.html b/app/views/ck_upload/image_browse.html new file mode 100644 index 0000000..bfbf25b --- /dev/null +++ b/app/views/ck_upload/image_browse.html @@ -0,0 +1,15 @@ + + +{literal} + +{/literal} + +

    {$title}

    +{$form} diff --git a/app/views/ck_upload/media_browse.html b/app/views/ck_upload/media_browse.html new file mode 100644 index 0000000..808b96a --- /dev/null +++ b/app/views/ck_upload/media_browse.html @@ -0,0 +1,20 @@ + + +{literal} + +{/literal} + +

    {$title}

    +{$form} diff --git a/app/views/cms_asset_info/viewlist.html b/app/views/cms_asset_info/viewlist.html new file mode 100644 index 0000000..0377bbb --- /dev/null +++ b/app/views/cms_asset_info/viewlist.html @@ -0,0 +1,14 @@ +New {$asset_name} + +
    +
      +{foreach from=$rows item=row} +
    • {$row._headline}     +{link_to href="controller:`$asset`;action:show;id:`$row.id`;" text="Show" class="control"} +{link_to href="controller:`$asset`;action:edit;id:`$row.id`;" text="Edit" class="control"} +{if !isset($row._remove_delete) || !$row._remove_delete} +{link_to href="controller:`$asset`;action:delete;id:`$row.id`;" text="Delete" class="control" confirm="Are you sure?"} +{/if} {check_asset_model asset=$row.asset} {check_asset_controller asset=$row.asset} {check_asset_table asset=$row.asset} +{/foreach} +
    +
    diff --git a/app/views/cms_asset_template/edit.html b/app/views/cms_asset_template/edit.html new file mode 100644 index 0000000..8933630 --- /dev/null +++ b/app/views/cms_asset_template/edit.html @@ -0,0 +1,5 @@ +

    Editing {$asset_name}

    + +{$form} + +List diff --git a/app/views/cms_asset_template/show.html b/app/views/cms_asset_template/show.html new file mode 100644 index 0000000..65254a7 --- /dev/null +++ b/app/views/cms_asset_template/show.html @@ -0,0 +1,10 @@ +

    Show {$asset_name}

    +{if $headline}

    {$headline}

    {/if} +
    +{foreach from=$row key=key item=val} +{$key|humanize}: {$val}
    +{/foreach} +
    + +Edit | +List \ No newline at end of file diff --git a/app/views/cms_asset_template/viewlist.html b/app/views/cms_asset_template/viewlist.html new file mode 100644 index 0000000..716a029 --- /dev/null +++ b/app/views/cms_asset_template/viewlist.html @@ -0,0 +1,16 @@ +{link_to href="controller:`$asset`;action:create;id:`$page_template_container_id`;" text="New `$asset_name`" class="btn btn-sm"} for container {$page_template_container_name} in template {$page_template_name} - {$page_template_filename}.html {check_template filename=`$page_template_filename`} + +{if $notice ne ''}{$notice}{/if} + +
    +
      +{foreach from=$rows item=row} +
    • {$row._headline}     +{link_to href="controller:`$asset`;action:edit;id:`$row.id`;" text="Edit" class="control"} +{if !isset($row._remove_delete) || !$row._remove_delete} +{link_to href="controller:`$asset`;action:delete;id:`$row.id`;" text="Delete" class="control" confirm="Are you sure?"} +{/if} + - {$row.template_filename}.html {check_asset_template filename=$row.template_filename asset=$row.asset} {check_asset_template_use asset=`$row._headline` container_id=`$page_template_container_id`} +{/foreach} +
    +
    diff --git a/app/views/code_caller/default.html b/app/views/code_caller/default.html new file mode 100644 index 0000000..be51f80 --- /dev/null +++ b/app/views/code_caller/default.html @@ -0,0 +1,3 @@ +{$_EDIT_START_} +{$content} +{$_EDIT_END_} diff --git a/app/views/content/list_assets.html b/app/views/content/list_assets.html new file mode 100644 index 0000000..087e9fe --- /dev/null +++ b/app/views/content/list_assets.html @@ -0,0 +1,7 @@ +
    + +
    \ No newline at end of file diff --git a/app/views/dashboard/dashboard_client_content.html b/app/views/dashboard/dashboard_client_content.html new file mode 100644 index 0000000..941862d --- /dev/null +++ b/app/views/dashboard/dashboard_client_content.html @@ -0,0 +1 @@ + diff --git a/app/views/dashboard/dashboard_client_sidebar_content.html b/app/views/dashboard/dashboard_client_sidebar_content.html new file mode 100644 index 0000000..56d7096 --- /dev/null +++ b/app/views/dashboard/dashboard_client_sidebar_content.html @@ -0,0 +1 @@ + diff --git a/app/views/dashboard/description.html b/app/views/dashboard/description.html new file mode 100644 index 0000000..c660252 --- /dev/null +++ b/app/views/dashboard/description.html @@ -0,0 +1,12 @@ +

    The Dashboard

    + +

    +The Dashboard is your nterchange home page. It gives you an overview of what the website is doing and provides the links to where you can find out more. +

    + +{if $drafts} +

    Drafts

    +

    +Any Drafts that you have saved appear here as a reminder that they are waiting for your completion. +

    +{/if} \ No newline at end of file diff --git a/app/views/dashboard/draft_record.html b/app/views/dashboard/draft_record.html new file mode 100644 index 0000000..5006b92 --- /dev/null +++ b/app/views/dashboard/draft_record.html @@ -0,0 +1,13 @@ +
    +You have the {$asset} asset named "{$cms_headline}" saved as a draft. +
    +  {link_to href="controller:`$asset`;action:show;id:`$id`;" text="Show" referer=1} +| {link_to href="controller:`$asset`;action:edit;id:`$id`;" text="Edit" referer=1} +{if $cms_draft} +{assign var="delete_msg" value="Since this content has never been published, it will be completely deleted.\\n\\nAre you sure?"} +{else} +{assign var="delete_msg" value="Are you sure?"} +{/if} +| {link_to href="controller:cms_drafts;action:delete;id:`$draft.id`;" text="Delete Draft" confirm="`$delete_msg`" referer=1} +
    +
    \ No newline at end of file diff --git a/app/views/dashboard/index.html b/app/views/dashboard/index.html new file mode 100644 index 0000000..e7767c6 --- /dev/null +++ b/app/views/dashboard/index.html @@ -0,0 +1,14 @@ +{if $drafts} +

    Drafts

    + {$drafts} +{/if} + +{version_check} + +{if $workflow} +

    Workflow Groups

    +{$workflow} +{/if} + +{* To put client data in the main content area - look in app/views/dashboard/dashboard_client_content.html *} +{dashboard_client_content} \ No newline at end of file diff --git a/app/views/dashboard/no_drafts.html b/app/views/dashboard/no_drafts.html new file mode 100644 index 0000000..08b7cb7 --- /dev/null +++ b/app/views/dashboard/no_drafts.html @@ -0,0 +1,3 @@ +

    +You do not have any drafts saved. +

    \ No newline at end of file diff --git a/app/views/dashboard/no_workflows.html b/app/views/dashboard/no_workflows.html new file mode 100644 index 0000000..dc7faed --- /dev/null +++ b/app/views/dashboard/no_workflows.html @@ -0,0 +1,3 @@ +

    +You do not belong to any workflow groups. +

    \ No newline at end of file diff --git a/app/views/dashboard/nterchange_training.html b/app/views/dashboard/nterchange_training.html new file mode 100644 index 0000000..b9c0052 --- /dev/null +++ b/app/views/dashboard/nterchange_training.html @@ -0,0 +1,5 @@ +

    nterchange Training

    + + \ No newline at end of file diff --git a/app/views/dashboard/workflow.html b/app/views/dashboard/workflow.html new file mode 100644 index 0000000..dfdc66f --- /dev/null +++ b/app/views/dashboard/workflow.html @@ -0,0 +1 @@ +

    {$workflow_title}

    diff --git a/app/views/dashboard/workflow_description.html b/app/views/dashboard/workflow_description.html new file mode 100644 index 0000000..9cdbd8f --- /dev/null +++ b/app/views/dashboard/workflow_description.html @@ -0,0 +1,4 @@ +

    Workflow

    +

    +Workflow controls the process that content takes to be published on the website. +

    \ No newline at end of file diff --git a/app/views/dashboard/workflow_norecords.html b/app/views/dashboard/workflow_norecords.html new file mode 100644 index 0000000..48223b0 --- /dev/null +++ b/app/views/dashboard/workflow_norecords.html @@ -0,0 +1,3 @@ +
    +There are no workflows currently in process for this group. +
    \ No newline at end of file diff --git a/app/views/dashboard/workflow_page_submit.html b/app/views/dashboard/workflow_page_submit.html new file mode 100755 index 0000000..fb74c7c --- /dev/null +++ b/app/views/dashboard/workflow_page_submit.html @@ -0,0 +1,7 @@ +{if $page_workflows} + +{/if} \ No newline at end of file diff --git a/app/views/dashboard/workflow_record.html b/app/views/dashboard/workflow_record.html new file mode 100644 index 0000000..91175df --- /dev/null +++ b/app/views/dashboard/workflow_record.html @@ -0,0 +1,29 @@ +{if $page_title} +
    +{$page_title} +
    +{/if} +
    +"{$row.cms_headline}" {if $asset->page_title}{$asset->page_title}{else}{$asset->name|humanize}{/if} asset on "{if !$page.cms_deleted}{$page.title}{else}{$page.title}{/if}" was {$action} @ {$workflow.cms_created|date_format:'%b %e, %Y %I:%M:%S %p'}{if $user} by {$user.real_name}{/if}. +{if !$list_only} +
    +  {link_to href="controller:page;action:surftoedit;id:`$page.id`;" text="Preview Page" rel="blank"} +{if $process == 'submit'} +| {link_to href="controller:workflow;action:submit;id:`$workflow.id`;" text="Submit To Workflow"} +| {link_to href="controller:`$asset->name`;action:edit;id:`$row.id`;" text="Edit Record" referer=1} +{if $cascade_delete} +| {link_to href="controller:workflow;action:delete;id:`$workflow.id`;" text="Delete Workflow" referer=1 confirm="Since this content has not yet been published on this page, it will also be removed from the page.\\n\\nAre you sure?"} +{else} +| {link_to href="controller:workflow;action:delete;id:`$workflow.id`;" text="Delete Workflow" referer=1 confirm="Are you sure?"} +{/if} +{elseif $process == 'editapprove'} +| {link_to href="controller:workflow;action:process;id:`$workflow.id`;" text="Edit & Approve/Decline"} +{elseif $process == 'approve'} +| {link_to href="controller:workflow;action:process;id:`$workflow.id`;" text="Approve/Decline"} +{elseif $process} +| {$process} +{/if} +
    +{/if} +
    + diff --git a/app/views/dashboard/workflow_section.html b/app/views/dashboard/workflow_section.html new file mode 100644 index 0000000..020df06 --- /dev/null +++ b/app/views/dashboard/workflow_section.html @@ -0,0 +1 @@ +
    {$workflow_section}
    \ No newline at end of file diff --git a/app/views/imageviewer/index.php b/app/views/imageviewer/index.php new file mode 100644 index 0000000..9ba34c1 --- /dev/null +++ b/app/views/imageviewer/index.php @@ -0,0 +1,68 @@ +$path) { + $i = $num+1; + if ($key == $num) { + $directions .= ' ' . $i; + } else { + $directions .= ' ' . $i . ''; + } + } +} +?> + + + + + Image Zoom<?php echo (($_GET['alt'])?' - ' . urldecode(stripslashes($_GET['alt'])):''); ?> + + + + + + + + + + + +
    + +<?php echo urldecode($alt); ?> border="0"> + +

    + +

    +
    + + + + diff --git a/app/views/layouts/default.html b/app/views/layouts/default.html new file mode 100644 index 0000000..7d8921a --- /dev/null +++ b/app/views/layouts/default.html @@ -0,0 +1,30 @@ +{include file="layouts/head" layout="default"} +{include file="layouts/navigation"} + +
    + +
    +
    +

    {$_TITLE_}

    + {dynamic}{if $_FLASH_.notice}
    {$_FLASH_.notice}
    {/if}{/dynamic} + {if $subnav} + {include file="layouts/subnav"} +
    + {/if} + {$MAIN_CONTENT} + {if $subnav} +
    + {/if} +
    +
     
    +
    + + {if $SIDEBAR_CONTENT} +
    +

    {$SIDEBAR_TITLE}

    + {$SIDEBAR_CONTENT} +
    + {/if} +
    + +{include file="layouts/tail"} diff --git a/app/views/layouts/head.html b/app/views/layouts/head.html new file mode 100644 index 0000000..cf30e3d --- /dev/null +++ b/app/views/layouts/head.html @@ -0,0 +1,44 @@ + + + + + + nterchange{if $_TITLE_} - {$_TITLE_}{/if} + + {stylesheet_link_tag href=screen} + {stylesheet_link_tag href=handheld media=handheld} + {stylesheet_link_tag href=nterchange_custom} + + + + + + + + + + {if $layout eq 'simple'} + {javascript_include_tag src=workflow} + {javascript_include_tag src=page_content} + {javascript_include_tag src=nterchange} + {/if} + + {if $layout eq 'default' || $layout eq 'plain'} + + {javascript_include_tag src=datechooser/datechooser} + {javascript_include_tag src=nterchange} + {/if} + + {if $layout eq 'default'} + {javascript_include_tag src=utils} + {javascript_include_tag src=application} + {javascript_include_tag src=workflow} + {javascript_include_tag src=page_content} + {javascript_include_tag src=page} + {javascript_include_tag src=ckeditor/ckeditor} + {/if} + + {$HEADER} + + + diff --git a/app/views/layouts/login.html b/app/views/layouts/login.html new file mode 100644 index 0000000..5c529b0 --- /dev/null +++ b/app/views/layouts/login.html @@ -0,0 +1,28 @@ +{include file="layouts/head" layout="login"} +{include file="layouts/navigation"} +{literal}{/literal} + +{if $_FLASH_.notice}
    {$_FLASH_.notice}
    {/if} + +
    +
    +
    +

    {$_TITLE_}

    + {$MAIN_CONTENT} + {if !$forgot} + + {/if} + {php} + print '
    ' . "\n"; + print 'Browser: ' . $_SERVER['HTTP_USER_AGENT'] . "
    \n"; + print 'IP: ' . $_SERVER['REMOTE_ADDR'] . "\n"; + print "
    \n"; + {/php} +
    +
     
    +
    +
    + +{include file="layouts/tail"} diff --git a/app/views/layouts/logo.html b/app/views/layouts/logo.html new file mode 100644 index 0000000..75cfeaa --- /dev/null +++ b/app/views/layouts/logo.html @@ -0,0 +1,3 @@ + diff --git a/app/views/layouts/navigation.html b/app/views/layouts/navigation.html new file mode 100644 index 0000000..5000974 --- /dev/null +++ b/app/views/layouts/navigation.html @@ -0,0 +1,15 @@ + diff --git a/app/views/layouts/plain.html b/app/views/layouts/plain.html new file mode 100644 index 0000000..eb9a1a7 --- /dev/null +++ b/app/views/layouts/plain.html @@ -0,0 +1,20 @@ +{include file="layouts/head" layout="plain"} +{include file="layouts/navigation"} + +
    +
    +
    +

    {$_TITLE_}

    + {if $_FLASH_.notice}
    {$_FLASH_.notice}
    {/if} + {$MAIN_CONTENT} +
    +
     
    +
    + {if $SIDEBAR_CONTENT} +
    + {$SIDEBAR_CONTENT} +
    + {/if} +
    + +{include file="layouts/tail"} diff --git a/app/views/layouts/previewobject.html b/app/views/layouts/previewobject.html new file mode 100755 index 0000000..217bfd4 --- /dev/null +++ b/app/views/layouts/previewobject.html @@ -0,0 +1,14 @@ + + + + + + Content Preview + {stylesheet_link_tag href=reset} + {stylesheet_link_tag href=default} + {javascript_include_tag src=utils} + {javascript_include_tag src=application} + + +{$MAIN_CONTENT} + diff --git a/app/views/layouts/simple.html b/app/views/layouts/simple.html new file mode 100644 index 0000000..d515027 --- /dev/null +++ b/app/views/layouts/simple.html @@ -0,0 +1,16 @@ +{include file="layouts/head" layout="simple"} + +
    +
    +
    +

    {$_TITLE_}

    + {if $_FLASH_.notice}
    {$_FLASH_.notice}
    {/if} + {$MAIN_CONTENT} +
    +
     
    +
    +
    +
     
    + + + diff --git a/app/views/layouts/subnav.html b/app/views/layouts/subnav.html new file mode 100644 index 0000000..3772618 --- /dev/null +++ b/app/views/layouts/subnav.html @@ -0,0 +1,9 @@ +{if $subnav} + +{/if} diff --git a/app/views/layouts/tail.html b/app/views/layouts/tail.html new file mode 100644 index 0000000..fa5e479 --- /dev/null +++ b/app/views/layouts/tail.html @@ -0,0 +1,9 @@ +
     
    + + + diff --git a/app/views/page/children_container.html b/app/views/page/children_container.html new file mode 100644 index 0000000..a8577a3 --- /dev/null +++ b/app/views/page/children_container.html @@ -0,0 +1,26 @@ + + + + New Page + + +{if is_array($pages) && count($pages) > 1} + + Re-order + +{/if} +{if is_array($pages) && count($pages)} +
    +
      + {foreach from=$pages item=page name=pages} +
    • +
      + + {if $page.active == 0}{$page.title|string_format:'[%s]'}{elseif $page.visible == 0}{$page.title|string_format:'(%s)'}{else}{$page.title}{/if} +
      +
    • + {/foreach} +
    +
    +{/if} + + diff --git a/app/views/page/content_container.html b/app/views/page/content_container.html new file mode 100644 index 0000000..55aa834 --- /dev/null +++ b/app/views/page/content_container.html @@ -0,0 +1,29 @@ +{if $reorder_link} + + Re-order + +{/if} + +{if is_array($contents) && !empty($contents)} +
    +
      + {foreach from=$contents item=content name=content} +
    • + {if !$no_edit} + + {/if} +
      + + {$content.cms_headline} ({$content._asset_name}) +
      +
    • + {/foreach} +
    +
    +{/if} + + diff --git a/app/views/page/content_container_title.html b/app/views/page/content_container_title.html new file mode 100644 index 0000000..ea70e58 --- /dev/null +++ b/app/views/page/content_container_title.html @@ -0,0 +1,2 @@ +

    {$container.container_name}

    + diff --git a/app/views/page/reorder_close.html b/app/views/page/reorder_close.html new file mode 100644 index 0000000..5c2dc1f --- /dev/null +++ b/app/views/page/reorder_close.html @@ -0,0 +1,4 @@ + + diff --git a/app/views/page/sidebar.html b/app/views/page/sidebar.html new file mode 100755 index 0000000..9a506fd --- /dev/null +++ b/app/views/page/sidebar.html @@ -0,0 +1,32 @@ +{if $surfedit && $page_id} +

    Surf to Edit

    +{link_to href="controller:page;action:surftoedit;id:`$page_id`;" text="Surf to Edit this page" rel="blank"} +{/if} + +{if $breadcrumbs} +

    Breadcrumbs

    + +{/if} + +{if $children} +

    Child Pages

    +
    +{foreach from=$children key=k item=child} +{if $k > 0} | {/if} +{assign var=title value=$child.title} +{if $child.visible == 0}{assign var=title value="(`$child.title`)"}{/if} +{if $child.active == 0}{assign var=title value="[`$child.title`]"}{/if} +{link_to href="action:`$action`;id:`$child.id`;" text=$title} +{/foreach} +
    +{/if} + +

    Utilities

    +

    {link_to href="controller:audit_trail;action:page;id:`$page_id`;" text="Page History"}

    +

    {link_to href="controller:page;action:delete_page_cache;id:`$page_id`;" text="Delete Page Cache"}

    \ No newline at end of file diff --git a/app/views/page/surftoedit.html b/app/views/page/surftoedit.html new file mode 100755 index 0000000..f2ee269 --- /dev/null +++ b/app/views/page/surftoedit.html @@ -0,0 +1,7 @@ + diff --git a/app/views/page_content/asset_add.html b/app/views/page_content/asset_add.html new file mode 100644 index 0000000..a6d47a8 --- /dev/null +++ b/app/views/page_content/asset_add.html @@ -0,0 +1,20 @@ + diff --git a/app/views/page_content/asset_edit.html b/app/views/page_content/asset_edit.html new file mode 100644 index 0000000..933756e --- /dev/null +++ b/app/views/page_content/asset_edit.html @@ -0,0 +1,14 @@ +
    + diff --git a/app/views/page_content/form.html b/app/views/page_content/form.html new file mode 100644 index 0000000..ba911ec --- /dev/null +++ b/app/views/page_content/form.html @@ -0,0 +1 @@ +{$form} \ No newline at end of file diff --git a/app/views/page_template/viewlist.html b/app/views/page_template/viewlist.html new file mode 100644 index 0000000..8ceb7f1 --- /dev/null +++ b/app/views/page_template/viewlist.html @@ -0,0 +1,15 @@ +New {$asset_name} + +
    +
      +{foreach from=$rows item=row} +
    • {$row._headline}     +{link_to href="controller:`$asset`;action:edit;id:`$row.id`;" text="Edit" class="control"} +{if !isset($row._remove_delete) || !$row._remove_delete} +{link_to href="controller:`$asset`;action:delete;id:`$row.id`;" text="Delete" class="control" confirm="Are you sure?"} +{/if} +{link_to href="controller:page_template_containers;action:viewlist;id:`$row.id`;" text="Containers" class="control"} + - {$row.template_filename}.html {check_template filename=$row.template_filename} +{/foreach} +
    +
    diff --git a/app/views/page_template_containers/edit.html b/app/views/page_template_containers/edit.html new file mode 100644 index 0000000..0f1b37b --- /dev/null +++ b/app/views/page_template_containers/edit.html @@ -0,0 +1,5 @@ +

    Editing {$asset_name}

    + +{$form} + +List \ No newline at end of file diff --git a/app/views/page_template_containers/show.html b/app/views/page_template_containers/show.html new file mode 100644 index 0000000..bff3c6c --- /dev/null +++ b/app/views/page_template_containers/show.html @@ -0,0 +1,10 @@ +

    Show {$asset_name}

    +{if $headline}

    {$headline}

    {/if} +
    +{foreach from=$row key=key item=val} +{$key|humanize}: {$val}
    +{/foreach} +
    + +Edit | +List \ No newline at end of file diff --git a/app/views/page_template_containers/viewlist.html b/app/views/page_template_containers/viewlist.html new file mode 100644 index 0000000..1aca21f --- /dev/null +++ b/app/views/page_template_containers/viewlist.html @@ -0,0 +1,17 @@ +{link_to href="controller:`$asset`;action:create;id:`$page_template_id`;" text="New `$asset_name`" class="btn btn-sm"} for template {$page_template_name} - {$page_template_filename}.html {check_template filename=$page_template_filename} + +{if $notice ne ''}{$notice}{/if} + +
    +
      +{foreach from=$rows item=row} +
    • {$row._headline}     +{link_to href="controller:`$asset`;action:edit;id:`$row.id`;" text="Edit" class="control"} +Delete +{*{if !isset($row._remove_delete) || !$row._remove_delete} +{link_to href="controller:`$asset`;action:delete;id:`$row.id`;" text="Delete" class="control" confirm="Are you sure?"} +{/if}*} +{link_to href="controller:cms_asset_template;action:viewlist;id:`$row.id`;" text="Connected Assets" class="control"}
    • +{/foreach} +
    +
    diff --git a/app/views/rss/audit_trail.html b/app/views/rss/audit_trail.html new file mode 100644 index 0000000..22c0694 --- /dev/null +++ b/app/views/rss/audit_trail.html @@ -0,0 +1,21 @@ + + + + audit trail rss feed - {$_SITE_NAME_} + + {$smarty.now|date_format:'%Y-%m-%d'}T00:00:00-07:00 + + nterchange + do-not-reply@nterchange.com + + urn:uuid:{$smarty.server.SERVER_NAME} +{foreach from=$records item=record} + + {$record.user.real_name} {$record.action_taken} a {$record.asset_name} + + urn:uuid:{$smarty.server.SERVER_NAME}:audit_trail{$record.id} + {$record.cms_created}T00:00:00-07:00 + {$record.user_id} {$record.action_taken} a {$record.asset_type} (id: {$record.asset.id}) on {if $record.page ne ''}page {$record.page.title} {/if}from IP address {$record.ip} at {$record.created} + +{/foreach} + \ No newline at end of file diff --git a/app/views/settings/viewlist.html b/app/views/settings/viewlist.html new file mode 100644 index 0000000..3b0a8fe --- /dev/null +++ b/app/views/settings/viewlist.html @@ -0,0 +1,7 @@ +

    +Below are the available Settings that allow you to customize how nterchange works for you. +

    + +{foreach from=$settings item=setting} +{$setting->toHTML()} +{/foreach} diff --git a/app/views/site_admin/clear_all_cache.html b/app/views/site_admin/clear_all_cache.html new file mode 100644 index 0000000..65bacc4 --- /dev/null +++ b/app/views/site_admin/clear_all_cache.html @@ -0,0 +1,9 @@ +

    +Caches have been cleared from: +

    + +
      +
    • Template compiling directory
    • +
    • View cache
    • +
    • ntercache
    • +
    diff --git a/app/views/site_admin/site_admin.html b/app/views/site_admin/site_admin.html new file mode 100644 index 0000000..340c864 --- /dev/null +++ b/app/views/site_admin/site_admin.html @@ -0,0 +1,19 @@ +

    +{if $page_edit} + + Surf To Edit + + + + New Page + +{/if} +{if $user_level >= 2} + + Clear All Caches + +{/if} +

    +{$content} +
    +{$sitemap_list} +
    diff --git a/app/views/site_admin/site_admin_list_end.html b/app/views/site_admin/site_admin_list_end.html new file mode 100644 index 0000000..776b4ca --- /dev/null +++ b/app/views/site_admin/site_admin_list_end.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/views/site_admin/site_admin_list_start.html b/app/views/site_admin/site_admin_list_start.html new file mode 100644 index 0000000..b9ee825 --- /dev/null +++ b/app/views/site_admin/site_admin_list_start.html @@ -0,0 +1,2 @@ +
      + diff --git a/app/views/site_admin/sitemap_list_item.html b/app/views/site_admin/sitemap_list_item.html new file mode 100644 index 0000000..f367e74 --- /dev/null +++ b/app/views/site_admin/sitemap_list_item.html @@ -0,0 +1,19 @@ +
    • + +
      + + {if $active == 0}{$title|string_format:'[%s]'}{elseif $visible == 0}{$title|string_format:'(%s)'}{else}{$title}{/if} + {if $workflow} ({$workflow}){/if} +
      +   +
    • diff --git a/app/views/templates/create.html b/app/views/templates/create.html new file mode 100644 index 0000000..00e4370 --- /dev/null +++ b/app/views/templates/create.html @@ -0,0 +1,5 @@ +

      Creating {$asset_name}

      + +{$form} + +List diff --git a/app/views/templates/edit.html b/app/views/templates/edit.html new file mode 100644 index 0000000..2bbe55d --- /dev/null +++ b/app/views/templates/edit.html @@ -0,0 +1,12 @@ +
      +

      Editing {$asset_name}

      +
      + +
      + {$form} +
      + +
      + Show | + List +
      diff --git a/app/views/templates/page_content.html b/app/views/templates/page_content.html new file mode 100644 index 0000000..c17ba7a --- /dev/null +++ b/app/views/templates/page_content.html @@ -0,0 +1,8 @@ +

      Pages this content belongs to

      +
      +
        +{foreach from=$pages item=page} +
      • {$page.title}     {link_to href="/_page`$page.id`" text="View Live" class="control" rel="blank"} | {link_to href="/nterchange/page/preview/`$page.id`" text="Preview" class="control" rel="blank"} +{/foreach} +
      +
      diff --git a/app/views/templates/show.html b/app/views/templates/show.html new file mode 100644 index 0000000..9510755 --- /dev/null +++ b/app/views/templates/show.html @@ -0,0 +1,13 @@ +

      Show {$asset_name}

      +{if $headline}

      {$headline}

      {/if} +
      +{foreach from=$row key=key item=val} +{$key|humanize}: {$val}
      +{/foreach} +
      + +

      +Edit | +List | +Create +

      \ No newline at end of file diff --git a/app/views/templates/sidebar_edit_status.html b/app/views/templates/sidebar_edit_status.html new file mode 100644 index 0000000..4b6a9a5 --- /dev/null +++ b/app/views/templates/sidebar_edit_status.html @@ -0,0 +1 @@ +{asset_edit_status} \ No newline at end of file diff --git a/app/views/templates/sidebar_page_content.html b/app/views/templates/sidebar_page_content.html new file mode 100644 index 0000000..71cad75 --- /dev/null +++ b/app/views/templates/sidebar_page_content.html @@ -0,0 +1,8 @@ +

      Pages this content belongs to

      +
      +
        +{foreach from=$pages item=page} +
      • {$page.title}     {link_to href="/_page`$page.id`" text="View Live" class="control" rel="blank"} | {link_to href="/nterchange/page/preview/`$page.id`" text="Preview" class="control" rel="blank"} +{/foreach} +
      +
      diff --git a/app/views/templates/sidebar_versions.html b/app/views/templates/sidebar_versions.html new file mode 100644 index 0000000..9570e02 --- /dev/null +++ b/app/views/templates/sidebar_versions.html @@ -0,0 +1,30 @@ +

      Versions for this content

      +
      +{if $cms_modified} +

      Current version

      +
      +
        +
      • {$user.real_name|default:'Unknown'}
        +{$cms_modified|date_format:'%b %e, %Y %H:%M:%S'}
      • +
      +
      +{/if} + +{if $versions} +

      Previous version(s)

      +
        +{foreach from=$versions item=version} +
      • {$version.user.real_name|default:'Unknown'}
        +{$version.cms_modified|date_format:'%b %e, %Y %H:%M:%S'}     +{link_to href="/nterchange/version/view/`$version.id`" text="View" class="control" rel="blank" referer=1} | +{link_to href="/nterchange/version/reinstate/`$version.id`" text="Reinstate" class="control" confirm="Are you sure you want to reinstate this version?" referer=1} +{/foreach} +
      +

      {link_to href="/nterchange/version/delete_all/`$version.id`" text="Delete ALL OLD Versions and Uploaded Files" class="control" confirm="Are you sure you want to delete all old versions and uploaded files?" referer=1}

      +{else} +

      +There are no versions created for this content. +

      +{/if} + +
      diff --git a/app/views/templates/viewlist.html b/app/views/templates/viewlist.html new file mode 100644 index 0000000..746e6cd --- /dev/null +++ b/app/views/templates/viewlist.html @@ -0,0 +1,58 @@ + + + + +{if $paginate ne ''} +
      +Items {$paginate.first}-{$paginate.last} out of {$paginate.total} displayed. +
      +{/if} + +
      + + +{if $viewlist_fields} +{foreach from=$viewlist_fields item=field_item} + +{/foreach} +{else} + +{/if} + + +{foreach from=$rows item=row} + +{if $viewlist_fields} +{foreach from=$viewlist_fields item=field_item} + +{/foreach} +{else} + +{/if} + + +{/foreach} +
      {$field_item|humanize}   +{if $sort_array.field == $field_item && $sort_array.arrow_asc}{image_tag src=/nterchange/images/s_asc.gif}{/if} +{if $sort_array.field == $field_item && $sort_array.arrow_desc}{image_tag src=/nterchange/images/s_desc.gif}{/if} +Headline +{if $smarty.get.sort == 'cms_headline_desc'}{image_tag src=/nterchange/images/s_desc.gif}{/if} +{if $smarty.get.sort == 'cms_headline_asc'}{image_tag src=/nterchange/images/s_asc.gif}{/if} + 
      {$row[$field_item]}{$row._headline} +{link_to href="controller:`$asset`;action:show;id:`$row.id`;" text="Show" class="control"} +{link_to href="controller:`$asset`;action:edit;id:`$row.id`;" text="Edit" class="control"} +{if !isset($row._remove_delete) || !$row._remove_delete} +{link_to href="controller:`$asset`;action:delete;id:`$row.id`;" text="Delete" class="control" confirm="Are you sure?"} +{/if} +
      +
      + +{if $paging ne ''} +
      +{paginate_prev id="`$asset`"} {paginate_middle id="`$asset`"} {paginate_next id="`$asset`"} +
      +{/if} \ No newline at end of file diff --git a/app/views/text/default.html b/app/views/text/default.html new file mode 100644 index 0000000..0e39934 --- /dev/null +++ b/app/views/text/default.html @@ -0,0 +1,5 @@ +
      +{$_EDIT_START_} +{$content} +{$_EDIT_END_} +
      diff --git a/app/views/users/confirmation_email.html b/app/views/users/confirmation_email.html new file mode 100644 index 0000000..9ea4227 --- /dev/null +++ b/app/views/users/confirmation_email.html @@ -0,0 +1,7 @@ +Your password was requested to be reset from IP Address: {$ip} + +To confirm the request and email your new password - please click this link: + +{$public_site}nterchange/login/confirm_password_reset?token={$confirmation_token} + +If you have any questions, please contact your website team. diff --git a/app/views/users/password_email.html b/app/views/users/password_email.html new file mode 100644 index 0000000..dfb2357 --- /dev/null +++ b/app/views/users/password_email.html @@ -0,0 +1,10 @@ +Hello {$real_name}, + +Here is the login information you require: + +username: {$username} +password: {$password} + +To login, please click the following link or copy and paste it into your web browser's address bar: + +{$public_site}nterchange/ diff --git a/app/views/users/viewlist.html b/app/views/users/viewlist.html new file mode 100644 index 0000000..ba62b28 --- /dev/null +++ b/app/views/users/viewlist.html @@ -0,0 +1,14 @@ +New {$asset_name} + +
      +
        +{foreach from=$rows item=row} +
      • {$row._headline}     +{link_to href="controller:`$asset`;action:show;id:`$row.id`;" text="Show" class="control"} +{link_to href="controller:`$asset`;action:edit;id:`$row.id`;" text="Edit" class="control"} +{if !isset($row._remove_delete) || !$row._remove_delete} +{link_to href="controller:`$asset`;action:delete;id:`$row.id`;" text="Delete" class="control" confirm="Are you sure?"} +{/if} +{/foreach} +
      +
      diff --git a/app/views/version/view.html b/app/views/version/view.html new file mode 100644 index 0000000..fb8dd9c --- /dev/null +++ b/app/views/version/view.html @@ -0,0 +1,24 @@ + + + + + View Version + {stylesheet_link_tag href=screen} + {stylesheet_link_tag href=handheld media=handheld} + {stylesheet_link_tag href=nterchange_custom} + {javascript_include_tag src=utils} + {javascript_include_tag src=application} + + + +
      +{foreach from=$asset key=k item=val} +{if !preg_match('/^cms_/', $k) && $k != 'id'} +
      {$k}:
      {$val}
      +{/if} +{/foreach} +
      + + + diff --git a/app/views/version_check/dashboard_version_check.html b/app/views/version_check/dashboard_version_check.html new file mode 100644 index 0000000..32ce23d --- /dev/null +++ b/app/views/version_check/dashboard_version_check.html @@ -0,0 +1,11 @@ +

      Version Check

      +{if $upgrade ne ''} +

      Current Version: {$upgrade.version} - Download - Changelog - {$upgrade.release_date}

      + +

      Your Version: {$nterchange_version}

      + +

      To update, please visit our website.

      +{else if $uptodate ne ''} +

      Congratulations, your nterchange installation is up to date.

      +{/if} + diff --git a/app/views/workflow/disapproved.html b/app/views/workflow/disapproved.html new file mode 100644 index 0000000..9c1846f --- /dev/null +++ b/app/views/workflow/disapproved.html @@ -0,0 +1,7 @@ +

      +You have declined to approve the content and your comments have been sent to the author. If the author resubmits the content, you will receive another email requesting your approval. +

      + + \ No newline at end of file diff --git a/app/views/workflow/process.html b/app/views/workflow/process.html new file mode 100644 index 0000000..d1cc750 --- /dev/null +++ b/app/views/workflow/process.html @@ -0,0 +1,24 @@ +

      "{$workflow_group.workflow_title}" Workflow Group

      + +

      +The workflow for the "{$asset.cms_headline}" {$asset_name} record on the {$page.title} page awaits your Approval. +

      + +{if $pages} +
      +This content will be updated on multiple pages: +
      +
        +{foreach from=$pages item=page} +
      • {link_to href="controller:page;action:preview;id:`$page.id`;" rel=blank text=$page.title}
      • +{/foreach} +
      +{/if} + +{if $description} +

      +{$description} +

      +{/if} + +{$form} \ No newline at end of file diff --git a/app/views/workflow/published.html b/app/views/workflow/published.html new file mode 100644 index 0000000..0564c8c --- /dev/null +++ b/app/views/workflow/published.html @@ -0,0 +1,11 @@ +

      "{$workflow_group.workflow_title}" Workflow Group

      + +

      +The workflow for the "{$asset.cms_headline}" {$asset_name} record on the {$page.title} page has been published. +

      + +
      You can:
      + \ No newline at end of file diff --git a/app/views/workflow/removed.html b/app/views/workflow/removed.html new file mode 100644 index 0000000..5d85e11 --- /dev/null +++ b/app/views/workflow/removed.html @@ -0,0 +1,11 @@ +

      "{$workflow_group.workflow_title}" Workflow Group

      + +

      +The workflow for the "{$asset.cms_headline}" {$asset_name} record on the {$page.title} page has been removed. +

      + +
      You can:
      +
        +
      • View the live page
      • +
      • {link_to href="controller:dashboard;" text="Return to the dashboard"}
      • +
      \ No newline at end of file diff --git a/app/views/workflow/send.html b/app/views/workflow/send.html new file mode 100644 index 0000000..a507494 --- /dev/null +++ b/app/views/workflow/send.html @@ -0,0 +1,18 @@ +

      "{$workflow_group.workflow_title}" Workflow Group

      + +

      +The workflow for the "{$asset.cms_headline}" {$asset_name} record on the {$page.title} page has been submitted. +

      + +
      Emails were sent to:
      +{if !empty($users)} + +{else} +

      You were the next/only user in the list, so you have not been emailed.

      +{/if} + +{link_to href="controller:dashboard;" text="Return to Your Dashboard"} diff --git a/app/views/workflow_group/viewlist.html b/app/views/workflow_group/viewlist.html new file mode 100644 index 0000000..744851b --- /dev/null +++ b/app/views/workflow_group/viewlist.html @@ -0,0 +1,7 @@ +
      +{link_to text="New" referer=1 href="action:create;" class="control"} Workflow Group +
      + +
      +{$workflows} +
      diff --git a/app/views/workflow_group/workflow_description.html b/app/views/workflow_group/workflow_description.html new file mode 100644 index 0000000..a662012 --- /dev/null +++ b/app/views/workflow_group/workflow_description.html @@ -0,0 +1,4 @@ +

      Workflow Group Creation

      +

      +In this section you can create workflow groups, add and remove users to and from workflows and assign rights to those users. To apply your workflow group to a page or section, go to {link_to text="Site Admin" href="controller:page\naction:site_admin"}. +

      \ No newline at end of file diff --git a/app/views/workflow_group/workflow_group.html b/app/views/workflow_group/workflow_group.html new file mode 100644 index 0000000..1bf000d --- /dev/null +++ b/app/views/workflow_group/workflow_group.html @@ -0,0 +1,21 @@ +
      +
      +
      {$_headline}
      +{link_to href="/nterchange/$asset/edit/`$id`" text="Edit" class="control"} | {link_to href="/nterchange/$asset/delete/`$id`" text="Delete" confirm="Are you sure?" class="control"} +
      + {foreach from=$_users item=user name=workflow_users} + {include file="workflow_users/workflow_user.html" id=$user.id real_name=$user.real_name role=$user.role} + {/foreach} +
      +
      + +{image_tag src="/nterchange/images/indicator.gif" id="add_user_indicator_`$id`" style="display:none;"} + +
      +
      +
      diff --git a/app/views/workflow_users/workflow_user.html b/app/views/workflow_users/workflow_user.html new file mode 100644 index 0000000..c3b9d97 --- /dev/null +++ b/app/views/workflow_users/workflow_user.html @@ -0,0 +1,24 @@ +
      {$real_name} + - Role: +{php} +$role = $this->_tpl_vars['role']; +switch ($role) { + case WORKFLOW_ROLE_AUTHOR: + print 'Author'; + break; + case WORKFLOW_ROLE_EDITOR: + print 'Editor'; + break; + case WORKFLOW_ROLE_APPROVER: + print 'Approver'; + break; + case WORKFLOW_ROLE_ADMIN: + print 'Admin'; + break; +} +{/php} +   +Edit | +Delete +
      diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..0dda7df --- /dev/null +++ b/bower.json @@ -0,0 +1,21 @@ +{ + "name": "nterchange", + "version": "3.3.0", + "dependencies": { + "yepnope": "~1.5.4", + "yepnope-pathfilter": "~1.0.0", + "html5shiv": "~3.7.0", + "modernizr-ie": "~1.0.0", + "nwmatcher": "~1.3.1", + "selectivizr": "suderman/selectivizr", + "respond": "~1.4.2", + "jquery": "~1.10.2", + "jquery-ui": "~1.10.3", + "bootstrap": "~3.0.2", + "fastclick": "ftlabs/fastclick", + "toe.js": "suderman/toe.js", + "H5F": "suderman/H5F", + "text-shadow": "~1.0.0", + "ckeditor": "latest" + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f402201 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "nonfiction/nterchange", + "description": "Core libraries for nterchange CMS", + "version": "4.1.1", + "require": { + "amazonwebservices/aws-sdk-for-php": "1.6.2", + "nonfiction/nterchange_pear": "*" + }, + "require-dev": { + "phpunit/phpunit": "3.7.28" + }, + "autoload": { + "classmap": ["lib/", "app/"], + "files": [ + "vendor/Smarty/libs/Smarty.class.php", + "vendor/SmartyPaginate/SmartyPaginate.class.php" + ] + }, + "include-path": ["./", "vendor/", "lib/"] +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..21ef1ed --- /dev/null +++ b/composer.lock @@ -0,0 +1,508 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "20389ef47cc2ce221574a940631925e6", + "content-hash": "76c175336c226b1645947b4df28da0c3", + "packages": [ + { + "name": "amazonwebservices/aws-sdk-for-php", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/amazonwebservices/aws-sdk-for-php.git", + "reference": "65d6bc82d6e0cd095709c342a23c749dc2f3a012" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amazonwebservices/aws-sdk-for-php/zipball/65d6bc82d6e0cd095709c342a23c749dc2f3a012", + "reference": "65d6bc82d6e0cd095709c342a23c749dc2f3a012", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "suggest": { + "aws/aws-sdk-php": "You should consider upgrading to version 2 of the AWS SDK for PHP" + }, + "type": "library", + "autoload": { + "classmap": [ + "authentication/", + "extensions/", + "lib/", + "services/", + "utilities/", + "sdk.class.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP", + "homepage": "http://aws.amazon.com/sdkforphp/", + "keywords": [ + "amazon", + "aws", + "dynamodb", + "ec2", + "s3", + "sdk" + ], + "abandoned": "aws/aws-sdk-php", + "time": "2013-03-15 19:43:06" + }, + { + "name": "nonfiction/nterchange_pear", + "version": "0.0.2", + "source": { + "type": "git", + "url": "https://github.com/nonfiction/nterchange_pear.git", + "reference": "74e8f0fb59f4791892a989a4e01d07ed6fab7c34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nonfiction/nterchange_pear/zipball/74e8f0fb59f4791892a989a4e01d07ed6fab7c34", + "reference": "74e8f0fb59f4791892a989a4e01d07ed6fab7c34", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "description": "PEAR library dependencies for nterchange", + "time": "2013-07-23 21:06:31" + } + ], + "packages-dev": [ + { + "name": "phpunit/php-code-coverage", + "version": "1.2.18", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.2.0@stable", + "phpunit/php-token-stream": ">=1.1.3,<1.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2014-09-02 10:13:14" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-21 08:01:12" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2014-03-03 05:10:30" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.28", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d", + "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2.1", + "phpunit/php-file-iterator": ">=1.3.1", + "phpunit/php-text-template": ">=1.1.1", + "phpunit/php-timer": ">=1.0.4", + "phpunit/phpunit-mock-objects": "~1.2.0", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear/pear": "1.9.4" + }, + "suggest": { + "ext-json": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "phpunit/php-invoker": ">=1.1.0,<1.2.0" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2013-10-17 07:27:40" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2013-01-13 10:24:48" + }, + { + "name": "symfony/yaml", + "version": "v2.8.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995", + "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-02-23 07:41:20" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..de45f4b --- /dev/null +++ b/config/config.php @@ -0,0 +1,47 @@ + $value) { + putenv("$key=$value"); + } + return true; + } + + /** + * Set the default timezone + */ + public static function setTimezone($tz) { + date_default_timezone_set($tz); + } +} diff --git a/lib/NUpload.php b/lib/NUpload.php new file mode 100644 index 0000000..d594a3c --- /dev/null +++ b/lib/NUpload.php @@ -0,0 +1,102 @@ + + * @copyright 2013 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @link http://www.nterchange.com/ + * @since File available since Release 3.2 + */ +class NUpload { + /** + * $handler - handle to the concrete upload handler class + * @var subclass of NUpload + */ + private static $handler; + + /** + * $prefix - used in building the path to the uploads + * generally the format will be ROOT.$prefix.$filename + * @var string + */ + public static $prefix = 'upload'; + + /** + * NUpload::connect - Connect NUpload to the specified upload handler + * @param string $handler the class name of the upload handler + * @return null + */ + public static function connect($handler) { + if (getenv('UPLOAD_PREFIX')) { + self::$prefix = getenv('UPLOAD_PREFIX'); + } + + if (!class_exists($handler)) { + throw new Exception("Upload handler class doesn't exist", 1); + } + + $handler = new $handler; + + if (!($handler instanceof NUpload)) { + throw new Exception("Upload handler is not instance of NUpload: $handler", 1); + } + + self::$handler = $handler; + + if (method_exists($handler, 'init')) { + call_user_func(array(self::$handler, "init"), null); + } + + } + + /** + * NUpload::moveUpload - move a local file to the desired path + * + * @param string $src Full path to the source file + * @param string $target Relative path to the destination (be sure it's sanitized) + * @return string URL to the file, fully qualified if not saved to the host server + */ + public static function moveUpload($src, $target) { + if (!self::$handler) self::noInstanceError(); + self::debug("Moving file: $src to $target"); + return call_user_func(array(self::$handler, "moveUpload"), $src, $target); + } + + /** + * NUpload::deleteUpload - remove an uploaded file + * + * @param string $url The url of the file, subclasses must handle stripping + * prefixes or prepending the document root. + * @return boolean false only if file exists but cannot be deleted + */ + public static function deleteUpload($url) { + if (!self::$handler) self::noInstanceError(); + return call_user_func(array(self::$handler, "deleteUpload"), $src, $target); + } + + public static function debug( + $message, $t=N_DEBUGTYPE_INFO, $p=PEAR_LOG_DEBUG, $z = false) { + NDebug::debug($message, $t, $p, $z); + } + + private static function noInstanceError(){ + throw new NUploadException("NUpload instance not connected", 1); + } +} diff --git a/lib/NUpload/Local.php b/lib/NUpload/Local.php new file mode 100644 index 0000000..e25d394 --- /dev/null +++ b/lib/NUpload/Local.php @@ -0,0 +1,27 @@ + AmazonS3::ACL_PUBLIC, + 'contentType' => NFilesystem::getMimeType($src), // fileUpload probably won't require this + 'fileUpload' => $src + ); + + $resp = $s3->create_object(self::$bucket, $target, $file_opts); + + if ($resp->status == 200) { + $url = $s3->get_object_url(self::$bucket, $target); + self::debug("File uploaded to s3: $url"); + return $url; + } else { + throw new NUploadException("Couldn't upload file to s3: $bucket - $filename", 1); + return ''; + } + } + + /** + * Implementation of NUpload::deleteUpload + */ + public static function deleteUpload($url) { + throw new NUploadException("deleteUpload is not implemented for S3 yet", 1); + } + + + private static function s3_connect(){ + CFCredentials::set(array( + 'development' => array( + 'key' => self::$api_key, + 'secret' => self::$secret_key, + 'default_cache_config' => 'apc', + 'certificate_authority' => false + ), + '@default' => 'development' + )); + + $s3 = new AmazonS3(); + return $s3; + } + +} diff --git a/lib/app_controller.php b/lib/app_controller.php new file mode 100644 index 0000000..f850038 --- /dev/null +++ b/lib/app_controller.php @@ -0,0 +1,907 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class AppController extends NController { + /** + * Sets of authentication for the controller + * + * Can either be an array of actions that are password-protected or + * boolean for all or none. + * + * @var mixed + * @access public + */ + var $login_required = array(); + + /** + * Sets level of user required to access login_required actions + * + * Can be one of N_USER_NORIGHTS, N_USER_EDITOR, + * N_USER_ADMIN or N_USER_ROOT, which are defined in n_auth.php + * + * @var mixed + * @access public + */ + var $user_level_required = N_USER_EDITOR; + + /** + * Versioning for controller + * + * Whether versions should be kept for records + * + * @todo This needs to get moved to the NModel class + * @var boolean + * @access public + */ + var $versioning = false; + + /** + * Constructor + * + * The constructor should be called all the way up the inheritance tree. + * Here, the CRUD methods are all set as $login_required and the default + * $base_view_dir is set if it hasn't been already. + * + * It is called implicitly on object instantiation. + * + * @access private + * @return null + */ + function __construct() { + if (is_null($this->base_view_dir)) + $this->base_view_dir = ROOT_DIR; + if (is_array($this->login_required)) { + $this->login_required = array_merge(array('viewlist', 'show', 'create', 'insert', 'edit', 'update', 'delete'), $this->login_required); + } + parent::__construct(); + } + + /** + * Checks if the _auth->user_level is high enough + * + * This method checks if _auth is set and, if so, if the current + * user_level is high enough + * + * @access public + * @return boolean + */ + function checkUserLevel() { + if (isset($this->_auth)) { + if ($this->_auth->getAuthData('user_level') < $this->user_level_required) { + return false; + } + } + return true; + } + + /** + * Sets some app-specific variables and calls parent::render + * + * This method auto-loads the nterchange navigation if the options + * include a 'layout' + * + * @see NController::render, NView::render + * @access public + * @return null + */ + function render($options=array()) { + if (is_array($options) && isset($options['layout'])) { + $this->set('navigation', nterchangeController::navigation($this->name)); + if (isset($this->_auth) && is_object($this->_auth)) { + $this->set('login_name', $this->_auth->getAuthData('real_name')); + } + $this->set('nterchange_version', NTERCHANGE_VERSION); + } + return parent::render($options); + } + + /** + * Sets some app-specific variables and calls parent::renderLayout + * + * This method auto-loads the nterchange navigation + * + * @see NController::renderLayout, NView::renderLayout + * @access public + * @return null + */ + function renderLayout($layout, $main_content, $sidebar_content=null, $return=false) { + $this->set('navigation', nterchangeController::navigation($this->name)); + if (isset($this->_auth) && is_object($this->_auth)) { + $this->set('login_name', $this->_auth->getAuthData('real_name')); + } + $this->set('nterchange_version', NTERCHANGE_VERSION); + return parent::renderLayout($layout, $main_content, $sidebar_content, $return); + } + + // CRUD FUNCTIONALITY + /** + * Shows a list of records associated with the controllers default model + * + * Instantiates the model, loops through all associated records and prints + * the headlines for each, including links to create/edit/delete + * + * @param $parameter null Placeholder for a default passed $parameter. Ignored. + * @param $layout Default true. Whether to render in a layout. + * @access public + * @return null + */ + function viewlist($parameter=null, $layout=true) { + $this->auto_render = false; + $this->base_dir = APP_DIR; + $assigns = array(); + $assigns['TITLE'] = Inflector::humanize($this->name); + $model = &$this->getDefaultModel($this->name); + if ($model) { + $model->find(); + $pk = $model->primaryKey(); + $models = array(); + $i = 0; + while ($model->fetch()) { + $arr = $model->toArray(); + $arr['_headline'] = isset($arr['cms_headline']) && $arr['cms_headline']?$arr['cms_headline']:$model->makeHeadline(); + $models[] = $arr; + unset($arr); + } + $this->set(array('rows'=>$models, 'asset'=>$this->name, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + unset($models); + } + $this->render(array('layout'=>'default')); + } + + /** + * Prints a single record + * + * Fetches the record by the id of $parameter, loops through the fields + * and displays the appropriate values + * + * @param $parameter int the id of the record to show + * @param $layout Default true. Whether to render in a layout. + * @access public + * @return null + */ + function show($parameter, $layout=true) { + $this->auto_render = false; + $this->base_dir = APP_DIR; + $assigns = array(); + $assigns['TITLE'] = Inflector::humanize($this->name); + $model = &$this->getDefaultModel(); + $headline = ''; + if ($model && $model->get($parameter)) { + $this->convertDateTimesToClient($model); + if (SITE_DRAFTS) { + $draft_model = &$this->loadModel('cms_drafts'); + $draft_model->asset = $this->name; + $draft_model->asset_id = $parameter; + if ($draft_model->find()) { + // fill the local model with the draft info + $draft_model->fetch(); + $current_user_id = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + if ($current_user_id == $draft_model->cms_modified_by_user) { + $content = unserialize($draft_model->draft); + foreach ($content as $field=>$val) { + $model->$field = $val; + } + $this->flash->set('notice', 'You have saved the record as a draft.'); + } else { + $user_model = &$this->loadModel('cms_auth'); + $user_model->get($draft_model->cms_modified_by_user); + $this->flash->set('notice', 'This record has been saved as a draft by "' . $user_model->real_name . '".'); + unset($user_model); + } + } + } + $pk = $model->primaryKey(); + $row = $model->toArray(); + foreach ($row as $key=>$val) { + if ($key == 'cms_headline') + $headline = $val; + if (preg_match('/^cms_/', $key)) + unset($row[$key]); + if ($key == $pk) + unset($row[$key]); + if (is_array($model->bitmask_fields) && count($model->bitmask_fields)) { + $bitmask_keys = array_keys($model->bitmask_fields); + if (in_array($key, $bitmask_keys)) { + $bitmask_total = $row[$key]; + $value_str = ''; + $i = 0; + foreach($model->bitmask_fields[$key] as $bit=>$val) { + if($bit & $bitmask_total) { + if($i > 0) { + $value_str .= ', '; + } + $value_str .= $val; + $i ++; + } + } + $row[$key] = $value_str; + } + } + // Let's show any uploads as live links as well. + if (isset($row[$key])) { + if (preg_match('|^'.UPLOAD_DIR.'|', $row[$key])) { + $row[$key] = '' . $row[$key] . ''; + } + } + } + if (is_array($this->display_fields) && count($this->display_fields)) { + foreach ($row as $field=>$val) { + if (!in_array($field, $this->display_fields)) { + unset($row[$field]); + } + } + } + $this->set(array('headline'=>$headline, 'row'=>$row, 'asset'=>$this->name, 'asset_id'=>$model->$pk, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + } else { + $this->flash->set('notice', 'The specified record could not be found.'); + $this->flash->now('notice'); + } + $this->render(array('layout'=>'default')); + } + + /** + * Displays a Create form for the controller's default model + * + * Instantiates the model, fetches the form and displays it. + * Also takes care of validation prior to passing the values to insert() + * + * @see AppController::insert(); + * @param $parameter null Placeholder for a default passed $parameter. Ignored. + * @param $layout Default true. Whether to render in a layout. + * @access public + * @return null + */ + function create($parameter=null, $layout=true) { + $this->auto_render = false; + // load the model layer with info + $model = &$this->getDefaultModel(); + if ($model) { + // create the form + $cform = new ControllerForm($this, $model); + $form = &$cform->getForm(); + + // assign the info and render + $this->base_dir = APP_DIR; + $assigns = array(); + $assigns['TITLE'] = Inflector::humanize($this->name); + $fields = $model->fields(); + if ($form->validate() && $this->insert(true)) { + $this->flash->set('notice', 'Your record has been saved.'); + $pk = $model->primaryKey(); + $this->redirectTo('show', $model->$pk); + } else if ($model) { + $this->set(array('form'=>$form->toHTML(), 'asset'=>$this->name, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + } + } else { + $this->flash->set('notice', 'The specified table could not be found.'); + $this->flash->now('notice'); + } + $this->render(array('layout'=>'default')); + } + + /** + * Inserts the current model's values into a new record + * + * Grabs the default model, validates the values (if they haven't + * been yet), and calls the model's insert() method + * + * @see NModel::insert(); + * @param $validated boolean Whether the values are prevalidated or not. + * Defaults to false. + * @access public + * @return int The new id of the record + */ + function insert($validated=false) { + $model = &$this->getDefaultModel(); + if (!$model) { + return false; + } + $pk = $model->primaryKey(); + $fields = $model->fields(); + // create the form + $cform = new ControllerForm($this, $model); + $form = &$cform->getForm(); + if (!$validated && !$form->validate()) { + return false; + } + if (in_array('cms_created', $fields)) { + $model->cms_created = $model->now(); + } + if (in_array('cms_modified', $fields)) { + $model->cms_modified = $model->now(); + } + // set the user id if it's applicable and available + if (in_array('cms_modified_by_user', $fields)) { + $model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + } + return $form->process(array($cform, 'processForm')); + } + + /** + * Displays an Edit form for the controller's default model + * + * Instantiates the model, fetches the form and displays it. + * Also takes care of validation prior to passing the values to update() + * + * @see AppController::insert(); + * @param $parameter int The id of the record to be edited + * @param $layout Default true. Whether to render in a layout. + * @access public + * @return null + */ + function edit($parameter, $layout=true) { + $this->auto_render = false; + // Track the edit - this way we can keep track of the last edits of each person. + $current_user_id = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + /*$action_track = NModel::factory('action_track'); + $status = $action_track->checkAssetEditStatus($this->name, $parameter); + if ($status == false) $track = $action_track->trackCurrentEdit($current_user_id, $this->name, $parameter); + unset($action_track);*/ + // load the model layer with info + $model = &$this->getDefaultModel(); + if ($model && $model->get($parameter)) { + $this->convertDateTimesToClient($model); + $pk = $model->primaryKey(); + if (SITE_DRAFTS) { + $draft_model = &$this->loadModel('cms_drafts'); + $draft_model->asset = $this->name; + $draft_model->asset_id = $parameter; + if ($draft_model->find()) { + // fill the local model with the draft info + $draft_model->fetch(); + $current_user_id = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + if ($current_user_id == $draft_model->cms_modified_by_user) { + $content = unserialize($draft_model->draft); + foreach ($content as $field=>$val) { + $model->$field = $val; + } + $this->flash->set('notice', 'You are currently editing your draft of this record.'); + $this->flash->now('notice'); + } else { + $user_model = &$this->loadModel('cms_auth'); + $user_model->get($draft_model->cms_modified_by_user); + $this->flash->set('notice', 'This record has been saved as a draft by "' . $user_model->real_name . '".'); + $this->flash->now('notice'); + unset($user_model); + } + } + } + // create the form + $cform = new ControllerForm($this, $model); + $form = &$cform->getForm(); + + // is a page_content_id passed? + $page_content_id = $this->getParam('page_content_id')?(int) $this->getParam('page_content_id'):false; + if ($page_content_id) { + $page_content = &NController::factory('page_content'); + $page_content_model = &$page_content->getDefaultModel(); + $page_content_model->get($page_content_id); + $page_content->convertDateTimesToClient($page_content_model); + $page_model = $page_content_model->getLink('page_id', 'page'); + } + + // check if this content belongs to a different workflow group or is currently in process + $owned_content = false; + if (SITE_WORKFLOW) { + $workflow = &NController::factory('workflow'); + $workflow_group_model = false; + $workflow_model = false; + $user_rights = 0; + if ($page_content_id) { + $workflow_model = &$workflow->getDefaultModel(); + $workflow_model->page_content_id = $page_content_id; + $workflow_model->asset = $this->name; + $workflow_model->asset_id = $model->$pk; + $workflow_model->completed = 0; + if ($workflow_model->find(null, true)) { + $owned_content = true; + } + $workflow_group_model = &$workflow->getWorkflowGroup($page_model); + $user_rights = $workflow->getWorkflowUserRights($page_model); + } else if ($workflow_group_model = &$workflow->findContentWorkflowGroup($this)) { + if ($workflow_model = &$workflow->findContentWorkflow($workflow_group_model->{$workflow_group_model->primaryKey()}, $this)) { + $page_model = &$this->loadModel('page'); + if ($page_model->get($workflow_model->page_id)) { + $owned_content = true; + } + $user_rights = $workflow->getWorkflowUserRights($page_model); + } else { + $page_content = &NController::factory('page_content'); + $page_model = $page_content->getContentPage($this); + if ($page_model) { + $owned_content = true; + } + $user_rights = $workflow->getWorkflowUserRights($page_model); + } + } + if (!$owned_content || ($owned_content && $user_rights & WORKFLOW_RIGHT_EDIT)) { + if ($workflow_model && $workflow_model->{$workflow_model->primaryKey()}) { + $form->removeElement('__submit_draft__'); + $form->insertElementBefore(NQuickForm::createElement('submit', '__submit_workflow__', 'Start Workflow'), '__submit__'); + $form->removeElement('__submit__'); + $workflow_draft = unserialize($workflow_model->draft); + $form->setDefaults($workflow_draft); + } else if ($user_rights & WORKFLOW_RIGHT_EDIT) { + $form->insertElementBefore(NQuickForm::createElement('submit', '__submit_workflow__', 'Start Workflow'), '__submit__'); + $form->removeElement('__submit__'); + } + } else if ($owned_content) { + $this->flash->set('notice', 'The record you are attempting to edit belongs to the "' . $workflow_group_model->workflow_title . '" Workflow Group'); + $this->flash->now('notice'); + $this->set('MAIN_CONTENT', '

      Please go to the dashboard to continue.

      '); + $this->render(array('layout'=>'default')); + exit; + } + } + // if page_content_id or (it's workflow owned and this user has editing rights) + if ($page_content_id || ($owned_content && $user_rights & WORKFLOW_RIGHT_EDIT)) { + // add timed content + if ($owned_content && $user_rights & WORKFLOW_RIGHT_EDIT && $workflow_model) { + $form->setDefaults(array('timed_start'=>$workflow_model->timed_start, 'timed_end'=>$workflow_model->timed_end)); + } else if ($page_content_id) { + $form->setDefaults(array('timed_start'=>$page_content_model->timed_start, 'timed_end'=>$page_content_model->timed_end)); + } + $page_content_model = &NModel::factory('page_content'); + $timed_start_el = &ControllerForm::addElement('timed_start', $form, $page_content_model); + $timed_end_el = &ControllerForm::addElement('timed_end', $form, $page_content_model); + if ($timed_start_el) { + $form->insertElementBefore($form->removeElement('timed_start'), ($form->elementExists('__submit_workflow__')?'__submit_workflow__':'__submit__')); + } + if ($timed_end_el) { + $form->insertElementBefore($form->removeElement('timed_end'), ($form->elementExists('__submit_workflow__')?'__submit_workflow__':'__submit__')); + } + } + // assign the info and render + $this->base_dir = APP_DIR; + $assigns = array(); + $fields = $model->fields(); + if ($form->validate() && $this->update(true)) { + // If it validates and updates, then clear out the action track. + /*$action_track = NModel::factory('action_track'); + $action_track->completeCurrentEdit($current_user_id, $this->name, $parameter); + unset($action_track);*/ + $this->flash->set('notice', 'Your record has been saved.'); + $this->redirectTo('show', $parameter); + } else if ($model) { + $this->set($model->toArray()); + $this->set(array('form'=>$form->toHTML(), 'asset'=>$this->name, 'asset_name'=>$this->page_title?$this->page_title:Inflector::humanize($this->name))); + } + } else { + $this->flash->set('notice', 'The specified record could not be found.'); + $this->flash->now('notice'); + } + if ($this->getParam('layout')=='false') { $layout=false; } + $this->render(($layout?array('layout'=>'default'):null)); + } + + /** + * Updates the current model's values + * + * Grabs the default model, validates the values (if they haven't + * been yet), and calls the model's update() method + * + * @see NModel::update(); + * @param $validated boolean Whether the values are prevalidated or not. + * Defaults to false. + * @access public + * @return int The number of records affected (should be 1) + */ + function update($validated=false) { + $model = &$this->getDefaultModel(); + $pk = $model->primaryKey(); + $fields = $model->fields(); + if (!$model || !isset($this->params[$pk]) || $model->get($this->params[$pk])) { + return false; + } + // create the form (this also does the validation) + $cform = new ControllerForm($this, $model); + $form = &$cform->getForm(); + if (!$validated && !$form->validate()) { + return false; + } + if (in_array('cms_modified', $fields)) { + $model->cms_modified= $model->now(); + } + // set the user id if it's applicable and available + if (in_array('cms_modified_by_user', $fields)) { + $model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + } + return $form->process(array($cform, 'processForm')); + } + + /** + * Delete the current model's record + * + * Grabs the default model, loads the records and calls the + * model's delete() method if an id is present (ie. a record is loaded) + * + * @see NModel::insert(); + * @param $parameter int The id of the record to be deleted + * @access public + * @return null + */ + function delete($parameter) { + if (empty($parameter)) { + $this->redirectTo('viewlist'); + } + // load the model layer with info + $model = &$this->getDefaultModel(); + if (!$model) $this->redirectTo('viewlist'); + if ($model->get($parameter)) { + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail before delete so we don't lose the values + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$this->name, 'asset_id'=>$model->{$model->primaryKey()}, 'action_taken'=>AUDIT_ACTION_DELETE)); + unset($audit_trail); + } + $model->delete(); + if (isset($this->params['_referer']) && $this->params['_referer']) { + header('Location:' . urldecode($this->params['_referer'])); + exit; + } + $this->flash->set('notice', Inflector::humanize($this->name) . ' record deleted.'); + $this->postProcessForm($model->toArray()); + } + $this->redirectTo('viewlist'); + } + + function convertDateTimesToClient(&$model) { + $table = $model->table(); + foreach ($table as $field=>$def) { + if ($def & N_DAO_TIME) { + switch (true) { + case $def & N_DAO_DATE && $def & N_DAO_TIME: + $format = '%Y-%m-%d %H:%M:%S'; + break; + default: + $format = '%H:%M:%S'; + } + $model->$field = NDate::convertTimeToClient($model->$field, $format); + } + } + } + + /** + * Rule used by form to check for unique cms_headline field + * + * @access public + * @return boolean True if cms_headline is unique, false otherwise + */ + function uniqueHeadline($value) { + $model = &$this->getDefaultModel(); + $id = $model->{$model->primaryKey()}; + $model = &NModel::factory($this->name); + if ($model) { + $conditions = $id?$model->primaryKey() . '!=' . $id:''; + $model->cms_headline = $value; + if ($model->find(array('conditions'=>$conditions))) { + unset($model); + return false; + } + } + unset($model); + return true; + } + + // nterchange drafts + /** + * Saves a draft of the record + * + * Either updates an existing draft or inserts a new one. + * + * @access public + * @return boolean true on success, false on failure + */ + function saveDraft() { + if (!SITE_DRAFTS) return false; + $model = &$this->getDefaultModel(); + if (!$model) return false; + $pk = $model->primaryKey(); + $fields = $model->fields(); + $values = array(); + foreach ($fields as $field) { + if ($field != $pk && !preg_match('|^cms_|', $field)) // don't save any of the meta content + $values[$field] = $model->$field; + } + $version_id = 0; + $version_model = &$this->loadModel('cms_nterchange_versions'); + if ($version_model) { + $version_model->asset_id = $model->$pk; + if ($version_model->find(array('first'=>true, 'order_by'=>'cms_created DESC'), true)) { + $version_id = (int) $version_model->{$version_model->primaryKey()}; + } + unset($version_model); + } + $draft_model = &NModel::factory('cms_drafts'); + if ($draft_model) { + $draft_model->asset = $this->name; + $draft_model->asset_id = $model->$pk; + $draft_model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + $update = $draft_model->find(null, true); + $draft_model->version_id = $version_id; + $draft_model->draft = serialize($values); + $draft_model->cms_modified = $draft_model->now(); + if ($update) { + $ret = $draft_model->update(); + } else { + $draft_model->cms_created = $draft_model->now(); + $ret = $draft_model->insert(); + } + unset($draft_model); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$this->name, 'asset_id'=>$model->$pk, 'action_taken'=>AUDIT_ACTION_DRAFT_SAVE)); + unset($audit_trail); + } + return $ret; + } + return false; + } + + /** + * Retrieves a copy of a draft for the loaded default model + * + * Checks for the currently logged in user and fetches the draft + * that matches with their id, and the id and type of asset + * + * Takes the draft, loads it into the default model and returns + * an array of the draft values + * + * @access public + * @return mixed Draft $values array on success, false on failure + */ + function loadDraft() { + if (!SITE_DRAFTS) return false; + $model = &$this->getDefaultModel(); + if (!$model) return false; + $pk = $model->primaryKey(); + $fields = $model->fields(); + $draft_model = &$this->loadModel('cms_drafts'); + if ($draft_model) { + $draft_model->asset = $this->name; + $draft_model->asset_id = $model->$pk; + $draft_model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + if ($draft_model->find(null, true)) { + $values = unserialize($draft_model->draft); + foreach ($values as $field=>$val) { + if (in_array($field, $fields)) + $model->$field = $val; + } + unset($draft_model); + return $values; + } + } + return false; + } + + /** + * Checks if a draft exists + * + * Checks for the currently logged in user and checks the existence of a + * draft that matches with their id, and the id and type of asset + * + * @access public + * @return boolean true if a draft exists, false otherwise + */ + function isDraft() { + if (!SITE_DRAFTS) return false; + $model = &$this->getDefaultModel(); + if (!$model) return false; + $pk = $model->primaryKey(); + $draft_model = &$this->loadModel('cms_drafts'); + if ($draft_model) { + $draft_model->asset = $this->name; + $draft_model->asset_id = $model->$pk; + $draft_model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + if ($draft_model->find(null, true)) { + unset($draft_model); + return true; + } + unset($draft_model); + } + return false; + } + + /** + * Deletes a draft + * + * Checks for the currently logged in user and deletes a draft that + * matches with their id, and the id and type of asset. + * + * @access public + * @return boolean true if a draft exists, false otherwise + */ + function deleteDraft() { + if (!SITE_DRAFTS) return false; + $model = &$this->getDefaultModel(); + if (!$model) return false; + $pk = $model->primaryKey(); + $draft_model = &NModel::factory('cms_drafts'); + if ($draft_model) { + $draft_model->reset(); + $draft_model->asset = $this->name; + $draft_model->asset_id = $model->$pk; + $draft_model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + if ($draft_model->find()) { + while ($draft_model->fetch()) { + $draft_model->delete(); + } + unset($draft_model); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail->insert(array('asset'=>$this->name, 'asset_id'=>$model->$pk, 'action_taken'=>AUDIT_ACTION_DRAFT_DELETE)); + unset($audit_trail); + } + return true; + } + unset($draft_model); + } + return false; + } + + // nterchange versioning + /** + * Retrieves versions for the current record + * + * Retrieves a single or multiple versions for an asset of a certain id. + * Can also retrieve a specific version id. + * + * Sample: + * $controller->getVersions($id); // array of all versions + * $controller->getVersions($id, true); // single version array + * $controller->getVersions($id, false|true, $version_id); // single old version if the version id matches the asset/id + * + * @param $id int The id of the record to get the versions for + * @param $latest_only boolean Get all versions or just the latest + * @param $version_id int Only get this version id, if it applies (used for + * reinstating old versions, etc) + * @access public + * @return mixed array on success, false on failure + */ + function getVersions($id, $latest_only = false, $version_id = 0) { + // sanity check + $id = (int) $id; + if (!$id) return false; + $sql = 'SELECT * FROM cms_nterchange_versions'; + $sql .= ' WHERE object_id=' . $id; + $sql .= ' AND object=' . $this->db->quoteSmart($this->getObject()); + if ($version_id && settype($version_id, 'integer')) { + $sql .= ' AND id=' . $version_id; + } + $sql .= ' ORDER BY cms_created DESC'; + if ($latest_only) { + $sql = $this->db->modifyLimitQuery($sql, 0, 1); + } + $res = $this->db->query($sql); + if (DB::isError($res)) { + $ret = false; + } else { + if ($latest_only) { + $row = $this->db->getRow($sql, null, DB_FETCHMODE_ASSOC); + $ret = unserialize($row['version']); + $ret['cms_version_id'] = $row['id']; + } else { + $ret = array(); + while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) { + $version = unserialize($row['version']); + $version['cms_version_id'] = $row['id']; + $ret[] = $version; + } + } + } + return $ret; + } + + /** + * Inserts a new versions for the current record + * + * Serializes the records contents and insert them into the + * cms_nterchange_versions table, if it exists + * + * @access public + * @see ControllerForm::processForm() + * @return int id of new version + */ + function insertVersion() { + $model = &$this->getDefaultModel(); + if (!$model) return false; + $pk = $model->primaryKey(); + if (!isset($model->$pk) || empty($model->$pk)) return false; + $version_model = &$this->loadModel('cms_nterchange_versions'); + if (!$version_model) { + // raise error: no versioning model available + return false; + } + if (!$this->_versionDiff($model)) { + $this->debug('Version not changed for ' . $model->tableName() . ': ' . $model->$pk, 'VERSION'); + return false; + } + $this->debug($model->tableName() . ' ' . $model->$pk, 'VERSION insert'); + // load the current info from the db (this happens before the update) + $old_model = &NModel::factory($this->name); + $old_model->get($model->$pk); + // convert to client time to auto-convert back to server on update + $this->convertDateTimesToClient($old_model); + // insert the current info into the version + $version_model->asset = $this->name; + $version_model->asset_id = $old_model->$pk; + $version_model->version = serialize($old_model->toArray()); + $version_model->cms_created = $old_model->cms_created; + $version_model->cms_modified = $old_model->cms_modified; + $version_model->cms_modified_by_user = isset($this->_auth) && is_object($this->_auth)?$this->_auth->currentUserID():0; + $ret = $version_model->insert(); + unset($version_model); + unset($old_model); + return $ret; + } + + /** + * Checks the current live object record against the old database record + * + * @access private + * @see AppController::insertVersion() + * @return boolean true if the new version has changed, false otherwise + */ + function _versionDiff(&$model) { + $old_version = &NModel::factory($this->name); + $old_version->get($model->{$model->primaryKey()}); + $fields = $old_version->table(); + $diff = false; + foreach ($fields as $field=>$def) { + if ($field != 'cms_created' && $field != 'cms_modified' && $field != 'cms_modified_by_user') { // ignore metadata + if ($old_version->$field != $model->$field) { + $diff = true; + break; + } + } + } + $diff = ($diff == false && $old_version->cms_headline==$model->cms_headline)?false:true; + unset($old_version); + return $diff; + } + + /** + * Class destructor + * + * @access private + * @return null + */ + function __destruct() { + unset($this->_auth); + foreach ($this->models as $model) { + unset($model); + } + } +} +?> diff --git a/lib/controller/form.php b/lib/controller/form.php new file mode 100644 index 0000000..6d2399f --- /dev/null +++ b/lib/controller/form.php @@ -0,0 +1,832 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class ControllerForm { + var $form = null; + var $controller = null; + var $model = null; + + // options + var $model_name = ''; + var $date_format = 'Y-m-d'; + var $time_format = 'H:i:s'; + var $rule_required_message = '%s is a required field'; + + function ControllerForm(&$controller, &$model, $options=null) { + include_once 'n_quickform.php'; + $this->controller = &$controller; + $this->model = &$model; + $this->_loadOptions($options); + } + + function &getForm($form_name='', $method='POST', $action='', $target='_self', $attributes=null) { + if ($form_name == '') { + $form_name = $this->controller->name . '_form'; + } + if ($action == '') { + $action = $_SERVER['REQUEST_URI']; + } + $this->controller->preGenerateForm(); + $this->form = new NQuickForm($form_name, $method, $action, $target, $attributes); + $header_label = $this->controller->page_title?$this->controller->page_title:Inflector::humanize($this->controller->name); + if ($this->model->form_header) { + if (is_array($this->model->form_header)) { + foreach ($this->model->form_header as $field) { + if (!isset($this->model->$field)) + continue; + $header_label .= ' - ' . $this->model->$field; + } + } else if (isset($this->model->{$this->model->form_header}) && !is_array($this->model->{$this->model->form_header})) { + $header_label .= ' - ' . $this->model->{$this->model->form_header}; + } + } + $el = $this->form->addElement('header', '__header__', $header_label); + $el->setLabel(array($el->_label, "{$header_label} header")); + $table = $this->model->table(); + foreach ($table as $field=>$def) { + $this->loadField($field, $this->form); + } + $defaults = $this->model->form_field_defaults; + $defaults = $this->model->toArray(); + foreach ($this->model->form_field_defaults as $field=>$default) { + if (!isset($defaults[$field])) { + $defaults[$field] = $default; + } + } + $this->form->setDefaults($defaults); + if (!$this->model->getHeadline() && !PEAR::isError($this->form->getElement('cms_headline'))) { + $this->form->addRule('cms_headline', sprintf($this->rule_required_message, 'Headline'), 'required'); + } + $el = $this->form->addElement('submit', '__submit__', 'Save'); + $el->setLabel(array("", "submit")); + if (SITE_DRAFTS && isset($table['cms_draft'])) { + // $submit = &$this->form->removeElement('__submit__'); + $submit_draft = &$this->form->createElement('submit', '__submit_draft__', 'Save as Draft'); + $el = $this->form->addElement($submit_draft); + $el->setLabel(array("", "submit")); + // $this->form->addGroup(array($submit, $submit_draft), null, '', ' ', null, false); + } + $values = $this->form->getSubmitValues(); + if (isset($_GET['_referer']) || isset($values['_referer'])) { + $referer = isset($_GET['_referer'])?$_GET['_referer']:$values['_referer']; + $this->form->addElement('hidden', '_referer', urlencode($referer)); + } + $this->setFormRules(); + $this->controller->postGenerateForm($this->form); + + return $this->form; + } + + function &loadField($field, &$form) { + return ControllerForm::addElement($field, $form, $this->model); + } + + function &addElement($field, &$form, &$model) { + if (!$model || !$form) { + $ret = null; + return $ret; + } + if (in_array($field, $model->form_ignore_fields)) { + $ret = null; + return $ret; + } + if (is_array($model->form_display_fields) && count($model->form_display_fields) > 0 && !in_array($field, $model->form_display_fields)) { + $ret = null; + return $ret; + } + if (is_array($model->bitmask_fields) && count($model->bitmask_fields)) { + $bitmask_keys = array_keys($model->bitmask_fields); + if(in_array($field, $bitmask_keys)) { + $checkbox = array(); + foreach ($model->bitmask_fields[$field] as $bit=>$label) { + $checkbox[] = &NQuickForm::createElement('checkbox', $bit, null, $label); + } + return $form->addElement('group', $field, ControllerForm::getFieldLabel($field, $model), $checkbox, '
      '); + } + } + $table = $model->table(); + $def = isset($table[$field])?$table[$field]:false; + if (!$def) { + $ret = null; + return $ret; + } + if (isset($model->form_elements[$field])) { + $field_def = &$model->form_elements[$field]; + if (is_object($field_def) && is_a($field_def, 'HTML_QuickForm_Element')) { + return $form->addElement($field_def); + } else if (is_string($field_def)) { + $field_def = array($field_def); + } + if (is_array($field_def) && count($field_def) && count($field_def) < 3) { + // minimum length to have field type, name and label + for ($i=1;$i<3;$i++) { + if (!isset($field_def[$i])) { + if ($i == 1) { + // set the field name + $field_def[$i] = $field; + } else if ($field_def[0] != 'hidden' && $i == 2) { + // set the field label + $field_def[$i] = ControllerForm::getFieldLabel($field, $model); + } + } + } + } + $el = &call_user_func_array(array($form, 'createElement'), $field_def); + if (is_object($el) && is_a($el, 'HTML_QuickForm_Element')) { + $el->updateAttributes(ControllerForm::getFieldAttributes($field)); + return $form->addElement($el); + } + $ret = null; + return $ret; + } + if (preg_match('|^cms_|', $field) && $field != 'cms_headline') { + $ret = null; + return $ret; + } + + if ($field == 'id') { + return $form->addElement('hidden', 'id'); + } + $element_label = ControllerForm::getFieldLabel($field, $model); + if ($field == 'cms_headline') $element_label = 'Headline'; + $attributes = array(); + switch (true) { + case $def & N_DAO_DATE && $def & N_DAO_TIME: + // $elementName = null, $elementLabel = null, $options = array(), $attributes = null + $options = array('language'=>'en', 'format'=>'Y-m-d H:i', 'minYear'=>2000, 'maxYear'=>date('Y')+5); + $options = ControllerForm::getFieldOptions($field, $options, $model); + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('date', $field, $element_label, $options, $attributes); + $el->setLabel(array($el->_label, "{$field} date")); + break; + case $def & N_DAO_DATE: + $options = array('language'=>'en', 'format'=>'Y-m-d', 'minYear'=>2000, 'maxYear'=>date('Y')+5); + $options = ControllerForm::getFieldOptions($field, $options, $model); + $attributes = array(); + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('date', $field, $element_label, $options, $attributes); + $el->setLabel(array($el->_label, "{$field} date")); + break; + case $def & N_DAO_TIME: + $options = array('language'=>'en', 'format'=>'H:i:s'); + $options = ControllerForm::getFieldOptions($field, $options, $model); + $attributes = array(); + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('date', $field, $element_label, $options, $attributes); + $el->setLabel(array($el->_label, "{$field} date")); + break; + case $def & N_DAO_BOOL: + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('checkbox', $field, $element_label, null, $attributes); + $el->setLabel(array($el->_label, "{$field} checkbox")); + break; + case $def & N_DAO_INT: + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('text', $field, $element_label, $attributes); + $el->setLabel(array($el->_label, "{$field} text")); + break; + case $def & N_DAO_FLOAT: + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('text', $field, $element_label, $attributes); + $el->setLabel(array($el->_label, "{$field} text")); + break; + case $def & N_DAO_TXT: + $attributes = array('rows'=>15, 'cols'=>50); + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('textarea', $field, $element_label, $attributes); + $el->setLabel(array($el->_label, "{$field} textarea")); + break; + case $def & N_DAO_BLOB: + // do nothing here since binary fields shouldn't be displayed + break; + case $def & N_DAO_STR: + $attributes = ControllerForm::getFieldAttributes($field, $attributes, $model); + $el = &$form->addElement('text', $field, $element_label, $attributes); + $el->setLabel(array($el->_label, "{$field} text")); + break; + } + return $el; + } + + function setFormRules() { + $form = &$this->form; + if (is_array($this->model->form_required_fields)) { + foreach ($this->model->form_required_fields as $field) { + $el = &$form->getElement($field); + if (!PEAR::isError($el)) { + $el_type = $el->getType(); + $el_label = $el->getLabel(); + if ($el_type == 'file' || $el_type == 'cms_file') { + if ($el_type == 'cms_file') { + if (!isset($form->_defaultValues[$el->getName()]) || !$form->_defaultValues[$el->getName()]) { + $form->addRule($field, sprintf($this->rule_required_message, $this->getFieldLabel($field)), 'uploadedfile'); + } + $form->addFormRule(array($el, '_ruleCheckRemove')); + } else if ($el_type == 'file') { + $form->addRule($field, sprintf($this->rule_required_message, $this->getFieldLabel($field)), 'uploadedfile'); + } + } else if ($el_type == 'group' || is_a($el, 'HTML_QuickForm_group')) { + $form->addGroupRule($field, sprintf($this->rule_required_message, $el_label), 'required', null, 0, 'client'); + } else if ($el_type == 'fckeditor') { + $form->addRule($field, sprintf($this->rule_required_message, $el_label), 'required'); + } else { + $form->addRule($field, sprintf($this->rule_required_message, $el_label), 'required', null, 'client'); + } + } + } + } + if (is_array($this->model->form_rules)) { + foreach ($this->model->form_rules as $rule) { + $el = &$form->getElement($rule[0]); + if (!PEAR::isError($el)) { + $el_type = $el->getType(); + if ($el_type == 'group') { + call_user_func_array(array(&$form, 'addGroupRule'), $rule); + } else { + call_user_func_array(array(&$form, 'addRule'), $rule); + } + } + } + } + } + + function makeRemoteForm($options=array()) { + if (!$this->form) { + // TODO: raise error since we can't update a non-existent form. Must call getForm first. + return; + } + include_once 'view/helpers/javascript_helper.php'; + $form = &$this->form; + if ($validation = $form->getAttribute('onsubmit')) { + // try { var myValidator = validate_add_person_form_10; } catch(e) { return true; } return myValidator(this); + $validation = 'var validated=false; try {var myValidator = validate_' . $form->getAttribute('name') . '; } catch(e) { validated=true; } if (myValidator) { validated=myValidator(this); } '; + $options['condition'] = 'validated'; + } + if (isset($options['url']) && is_array($options['url']) && isset($options['url']['controller'])) { + $controller = &NController::singleton($options['url']['controller']); + } else { + $controller = &$this->controller; + } + $options['form'] = true; + $function = JavascriptHelper::remoteFunction($controller, $options); + $function .= '; return false;'; + $form->updateAttributes(array('onsubmit'=>$validation . $function)); + + } + + function processForm($values) { + $this->controller->preProcessForm($values); + $model = &$this->model; + $pk = $model->primaryKey(); + if (!$pk) { + // TODO: raise error - can't store data if there's no primary key + return false; + } + $table = $model->table(); + $fields = $model->fields(); + $action = 'update'; + if (!isset($model->$pk) || !strlen($model->$pk)) { + $action = 'insert'; + } + // setup for file uploads + $cms_files = array(); + $files = array(); + if (isset($_FILES) && is_array($_FILES)) { + $form = &$this->form; + foreach ($_FILES as $field=>$value) { + $el = &$form->getElement($field); + if ($el->getType() == 'file' || $el->getType() == 'cms_file') { + $values[$field] = null; + if ($el->getType() == 'cms_file') { + $cms_files[$field] = &$el; + } + if (isset($_FILES[$field]['tmp_name']) && is_uploaded_file($_FILES[$field]['tmp_name'])) { + $files[$field] = array('type'=>$el->getType(), 'upload_dir'=>(isset($el->_options['upload_dir'])?$el->_options['upload_dir']:UPLOAD_DIR), 'value'=>$value); + } else { + $values[$field] = isset($this->_do->$field)?$this->_do->$field:''; + } + } + } + } + foreach ($cms_files as $field=>$el) { + if (isset($values[$field.'__remove']) && $values[$field.'__remove']) { + $values[$field] = ''; + if (isset($files[$field])) { + unset($files[$field]); // so it doesn't get processed and uploaded after the fact + } + } else if (isset($values[$field.'__current']) && $values[$field.'__current']) { + $values[$field] = $values[$field.'__current']; + } + } + // pull in any boolean fields that weren't passed and should therefore be 0 + foreach ($table as $field=>$def) { + if ($def & N_DAO_BOOL && !preg_match('/^cms_/', $field)) { + // if the field is being ignored, then leave the value as what it is + if (in_array($field, $model->form_ignore_fields)) { + continue; + } + $values[$field] = isset($values[$field])?1:0; + } + } + // deal with general values + foreach ($values as $field=>$val) { + if (!in_array($field, $fields)) { + continue; + } + $def = $table[$field]; + switch (true) { + case ($def & N_DAO_DATE || $def & N_DAO_TIME) && is_array($val): + $val = NDate::arrayToDate($val); + if (!($def & N_DAO_NOTNULL)) { + if ($def & N_DAO_DATE || $def & N_DAO_TIME) { + if (!NDate::validDateTime($val)) { + $val = 'null'; + } + } + } + break; + } + $model->$field = $val; + } + // set the autoheadline if it exists and wasn't set manually + if (in_array('cms_headline', $fields) && !$values['cms_headline'] && $model->getHeadline()) { + $model->cms_headline = $model->makeHeadline('-'); + } + if ($action == 'update') { + $this->processFiles($values, $files); + foreach ($files as $field=>$val) { + if (!in_array($field, $fields)) { + continue; + } + $model->$field = $values[$field]; + } + $page_content_id = $this->controller->getParam('page_content_id')?$this->controller->getParam('page_content_id'):false; + if ($page_content_id) { + $page_content = &NController::singleton('page_content'); + $page_content_model = &$page_content->getDefaultModel(); + $page_content_model->get($page_content_id); + } + // set up timed contnt values if they are there + if (isset($values['timed_start'])) { + $values['timed_start'] = NDate::arrayToDate($values['timed_start']); + if (!NDate::validDateTime($values['timed_start'])) { + $values['timed_start'] = 'null'; + } + } + if (isset($values['timed_end'])) { + $values['timed_end'] = NDate::arrayToDate($values['timed_end']); + if (!NDate::validDateTime($values['timed_end'])) { + $values['timed_end'] = 'null'; + } + } + // check for workflow + if (SITE_WORKFLOW && isset($values['__submit_workflow__'])) { + $page_content = &NController::factory('page_content'); + if (!$page_content_id) { + // then find the page we're attached to + $page_content_model = &$page_content->getDefaultModel(); + $page_content_model->content_asset = $this->controller->name; + $page_content_model->content_asset_id = $model->$pk; + if ($page_content_model->find(null, true)) { + $page_content_id = $page_content_model->{$page_content_model->primaryKey()}; + } + } + if ($page_content_id) { + $page_model = $page_content_model->getLink('page_id', 'page'); + $page_id = $page_model->{$page_model->primaryKey()}; + // remove the draft and update the content + // delete the draft record + $this->controller->deleteDraft(); + // Pull a fresh copy of the asset model and set the draft to 0 + // so we don't update with the new content yet + $asset_model = &NModel::factory($this->controller->name); + $asset_model->get($model->$pk); + $asset_model->cms_draft = 0; + $asset_model->update(); + } + unset($page_content); + // save the workflow + $workflow = &NController::factory('workflow'); + $workflow_group_model = $workflow->getWorkflowGroup($page_model); + // set values for saveWorkflow() + $workflow_values = array(); + $workflow_values['page_content_id'] = $page_content_model->{$page_content_model->primaryKey()}; + $workflow_values['workflow_group_id'] = $workflow_group_model->{$workflow_group_model->primaryKey()}; + // add timed content + $workflow_values['timed_start'] = $values['timed_start']; + $workflow_values['timed_end'] = $values['timed_end']; + // unset the timed content values so they don't get pushed into page_content + unset($values['timed_start'], $values['timed_end']); + $ret = $workflow->saveWorkflow($workflow_values, WORKFLOW_ACTION_EDIT, $this->controller); + } else if ($page_content_id) { + $page_content_model->timed_start= $values['timed_start']; + $page_content_model->timed_end= $values['timed_end']; + $page_content_model->col_xs= $values['col_xs']; + $page_content_model->col_sm= $values['col_sm']; + $page_content_model->col_md= $values['col_md']; + $page_content_model->col_lg= $values['col_lg']; + $page_content_model->row_xs= $values['row_xs']; + $page_content_model->row_sm= $values['row_sm']; + $page_content_model->row_md= $values['row_md']; + $page_content_model->row_lg= $values['row_lg']; + $page_content_model->offset_col_xs= $values['offset_col_xs']; + $page_content_model->offset_col_sm= $values['offset_col_sm']; + $page_content_model->offset_col_md= $values['offset_col_md']; + $page_content_model->offset_col_lg= $values['offset_col_lg']; + $page_content_model->offset_row_xs= $values['offset_row_xs']; + $page_content_model->offset_row_sm= $values['offset_row_sm']; + $page_content_model->offset_row_md= $values['offset_row_md']; + $page_content_model->offset_row_lg= $values['offset_row_lg']; + $page_content_model->pull_xs= $values['pull_xs']; + $page_content_model->pull_sm= $values['pull_sm']; + $page_content_model->pull_md= $values['pull_md']; + $page_content_model->pull_lg= $values['pull_lg']; + $page_content_model->gutter_xs= $values['gutter_xs']; + $page_content_model->gutter_sm= $values['gutter_sm']; + $page_content_model->gutter_md= $values['gutter_md']; + $page_content_model->gutter_lg= $values['gutter_lg']; + $page_content_model->update(); + } + // check for drafts + if (SITE_DRAFTS) { + if (isset($table['cms_draft']) && isset($values['__submit_draft__'])) { + $ret = $this->controller->saveDraft(); + // update the headline immediately if it exists + if (isset($table['cms_headline'])) { + $tmp_model = &NModel::factory($this->controller->name); + if ($tmp_model && $tmp_model->get($values[$pk])) { + $tmp_model->cms_headline = $values['cms_headline']; + $tmp_model->update(); + } + unset($tmp_model); + } + } + } + if (isset($values['__submit__'])) { + if ($this->controller->versioning == true) { + if (!isset($values['__skip_versioning__'])) { + $this->controller->debug('Inserting new version for ' . $model->tableName() . ': ' . $model->$pk, 'VERSION'); + $version_id = $this->controller->insertVersion(); + } + } + // if it's being saved normally (no draft), make sure it's not marked as a draft + $model->cms_draft = 0; + $ret = $model->update(); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL && isset($this->controller->_auth)) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail_values = array(); + $audit_trail_values['asset'] = $this->controller->name; + $audit_trail_values['asset_id'] = $model->$pk; + $audit_trail_values['action_taken'] = AUDIT_ACTION_UPDATE; + if (isset($page_content_id)) { + $audit_trail_values['page_content_id'] = $page_content_id; + } + if (isset($page_id)) { + $audit_trail_values['page_id'] = $page_id; + } + $audit_trail->insert($audit_trail_values); + unset($audit_trail); + } + if (SITE_DRAFTS) { + // delete any draft records + $this->controller->deleteDraft(); + } + // remove all linked page caches + $page = &NController::factory('page'); + $page_content_model = &NModel::factory('page_content'); + $page_content_model->content_asset = $this->controller->name; + $page_content_model->content_asset_id = $values[$pk]; + if ($page_content_model->find()) { + while ($page_content_model->fetch()) { + $page->deletePageCache($page_content_model->page_id); + } + } + unset($page); + unset($page_content_model); + } + } else { + $draft = false; + if (SITE_DRAFTS && isset($table['cms_draft']) && isset($values['__submit_draft__'])) { + $model->cms_draft = 1; + } + $id = $model->insert(); + $this->processFiles($values, $files); + foreach ($files as $field=>$val) { + if (!in_array($field, $fields)) { + continue; + } + $model->$field = $values[$field]; + } + if (SITE_DRAFTS && isset($table['cms_draft']) && isset($values['__submit_draft__'])) { + // set draft to true after the draft is saved + $draft = $this->controller->saveDraft(); + } + $model->update(); + if (defined('SITE_AUDIT_TRAIL') && SITE_AUDIT_TRAIL && isset($this->controller->_auth)) { + // audit trail + $audit_trail = &NController::factory('audit_trail'); + $audit_trail_values = array(); + $audit_trail_values['asset'] = $this->controller->name; + $audit_trail_values['asset_id'] = $model->$pk; + $audit_trail_values['action_taken'] = AUDIT_ACTION_INSERT; + if (isset($page_content_id)) { + $audit_trail_values['page_content_id'] = $page_content_id; + } + if (isset($page_id)) { + $audit_trail_values['page_id'] = $page_id; + } + $audit_trail->insert($audit_trail_values); + unset($audit_trail); + } + $ret = $id; + } + if ($ret) { + $this->controller->postProcessForm($values); + } + if (isset($values['_referer'])) { + header('Location:' . urldecode($values['_referer'])); + exit; + } + return $ret; + } + + function processFiles(&$values, $files_array) { + include_once 'n_filesystem.php'; + $form = &$this->form; + $model = &$this->model; + $pk = $model->primaryKey(); + + foreach ($files_array as $field=>$vals) { + $tmp_file = $vals['value']['tmp_name']; + + if (!is_uploaded_file($tmp_file)) { + $values[$field] = ''; + continue; + } + + $path = array(); + + if ($vals['type'] == 'cms_file') { + $path[] = $this->controller->name; + $path[] = $model->$pk; + } + + $path[] = substr(md5(microtime()), 20); + $path[] = NFilesystem::cleanFileName($vals['value']['name']); + $filename = implode('/', $path); + + $tmp_file = $model->beforeUpload($field, $tmp_file); + + $newfile = NUpload::moveUpload($tmp_file, $filename); + + $values[$field] = $newfile ? $newfile : ''; + } + } + + function validDateTime($value, $def) { + if (!$def) return true; + if (($def & N_DAO_DATE && $def & N_DAO_TIME && preg_match('/0000-00-00 00:00(?::00)?/', $value)) || + ($def & N_DAO_DATE && $value == '0000-00-00') || + ($def & N_DAO_TIME && preg_match('/00:00(?::00)?/', $value))) { + return true; + } + return false; + } + + function exportValues($element_list = null) { + $vals = $this->form->exportValues($element_list); + $fields = $this->model->table(); + foreach ($vals as $field=>$val) { + if (!isset($fields[$field])) { + continue; + } + $def = $fields[$field]; + switch (true) { + case $def & N_DAO_DATE: + case $def & N_DAO_TIME: + if (is_array($vals[$field])) + $vals[$field] = NDate::arrayToDate($vals[$field]); + } + } + return $vals; + } + function getFieldLabel($field, $model=null) { + if (!$model && isset($this)) { + $model = $this->model; + } + if (!$model) return; + return isset($model->form_field_labels[$field])?$model->form_field_labels[$field]:Inflector::humanize($field); + } + + function getFieldOptions($field, $options=array(), $model=null) { + if (!$model && isset($this)) { + $model = $this->model; + } + if (!$model) return; + $opts = isset($model->form_field_options[$field])?$model->form_field_options[$field]:array(); + return array_merge($options, $opts); + } + + function getFieldAttributes($field, $attributes=array(), $model=null) { + if (!$model && isset($this)) { + $model = $this->model; + } + if (!$model) return; + $atts = isset($model->form_field_attributes[$field])?$model->form_field_attributes[$field]:array(); + return array_merge($attributes, $atts); + } + + function _loadOptions($options) { + if (is_array($options)) { + $this->model_name = isset($options['model_name'])?$options['model_name']:$this->controller->name; + } else { + $this->model_name = empty($options)?$this->controller->name:$options; + } + } +} + +require_once 'HTML/QuickForm/select.php'; +class HTML_QuickForm_foreignkey extends HTML_QuickForm_select { + function HTML_QuickForm_foreignkey($elementName=null, $elementLabel=null, $options=null, $attributes=null) { + if (!isset($options['model']) || !isset($options['headline'])) + return; + $add_empty_option = isset($options['addEmptyOption'])?(bool) $options['addEmptyOption']:false; + $model = $options['model']; + $headline = $options['headline']; + $separator = isset($options['separator'])?$options['separator']:' - '; + $options = array(); + $model = &NModel::factory($model); + if ($model && $model->find()) { + if ($add_empty_option) + $options[''] = 'Select...'; + $pk = $model->primaryKey(); + while ($model->fetch()) { + if (is_array($headline)) { + $label = ''; + foreach ($headline as $field) { + $label .= ($label?$separator:'') . $model->$field; + } + } else { + $label = $model->$headline; + } + $options[$model->$pk] = $label; + } + } + if (empty($options)) { + $options[0] = 'N/A'; + } + $this->HTML_QuickForm_select($elementName, $elementLabel, $options, $attributes); + $this->_type = 'foreignkey'; + } +} + +require_once 'HTML/QuickForm/textarea.php'; +class HTML_QuickForm_fckeditor extends HTML_QuickForm_textarea { + var $_editor = null; + var $_editor_config = array( + 'CustomConfigurationsPath' => null, + 'Debug' => null, + 'SkinPath' => null, + 'PluginsPath' => null, + 'AutoDetectLanguage' => null, + 'DefaultLanguage' => null, + 'EnableXHTML' => null, + 'EnableSourceXHTML' => null, + 'GeckoUseSPAN' => null, + 'StartupFocus' => null, + 'ForcePasteAsPlainText' => null, + 'ForceSimpleAmpersand' => null, + 'TabSpaces' => null, + 'UseBROnCarriageReturn' => null, + 'LinkShowTargets' => null, + 'LinkTargets' => null, + 'LinkDefaultTarget' => null, + 'ToolbarStartExpanded' => null, + 'ToolbarCanCollapse' =>null, + 'StylesXmlPath' => null + ); + function HTML_QuickForm_fckeditor($elementName=null, $elementLabel=null, $attributes=null, $options=null) { + include_once BASE_DIR . '/nterchange/javascripts/fckeditor/fckeditor.php'; + $this->_editor = new FCKEditor($elementName); + $settings_model = &NModel::factory('cms_settings'); + $editor_set = $settings_model->getSetting(SETTINGS_EDITOR); + unset($settings_model); + if ($editor_set == false || !$this->_editor->IsCompatible()) { + HTML_QuickForm_textarea::HTML_QuickForm_textarea($elementName, $elementLabel, $attributes); + // if the browser isn't compatible, then remove _editor parameter and set up as a textarea + unset($this->_editor); + $this->updateAttributes(array('rows'=>25, 'cols'=>60, 'style'=>'width:470px;height:500px;')); + } else { + HTML_QuickForm_element::HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_type = 'fckeditor'; + $this->_persistantFreeze = true; + // set base options and paths + $this->_editor->BasePath = '/nterchange/javascripts/fckeditor/'; + $this->_editor->Width = '470px'; + $this->_editor->Height = '500px'; + if (file_exists(DOCUMENT_ROOT . '/includes/fckstyles.xml')) { + $this->_editor_config['StylesXmlPath'] = '/includes/fckstyles.xml'; + } + if (file_exists(DOCUMENT_ROOT . '/includes/fcktemplates.xml')) { + $this->_editor_config['TemplatesXmlPath'] = '/includes/fcktemplates.xml'; + } + // overwrite any set $_editor_options with the passed $options, only allowing the ones that already exist + if (is_array($options)) { + foreach ($options as $option=>$val) { + if (in_array($option, array_keys($this->_editor_config))) { + $this->_editor_config[$option] = $val; + } + } + } + // load any $_editor_options that are not null into the $_editor->Config array + foreach ($this->_editor_config as $option=>$val) { + if (!is_null($val)) { + $this->_editor->Config[$option] = $val; + } + } + // point at nterchange custom configuration file + if (file_exists(DOCUMENT_ROOT . '/javascripts/fckconfig.js')) { + $this->_editor->Config['CustomConfigurationsPath'] = '/javascripts/fckconfig.js?' . filemtime(DOCUMENT_ROOT . '/javascripts/fckconfig.js'); + } else { + $this->_editor->Config['CustomConfigurationsPath'] = $this->_editor->BasePath . 'n_config.js?' . filemtime(BASE_DIR . '/nterchange/javascripts/fckeditor/n_config.js'); + } + // set the toolbar to our configured toolbar + $this->_editor->ToolbarSet = 'nonfiction'; + } + } + + function setName($name) { + $this->updateAttributes(array('name'=>$name)); + if (isset($this->_editor)) { + $this->_editor->InstanceName = $name; + } + } + + function getName() { + return $this->getAttribute('name'); + } + + function setValue($value) { + if (isset($this->_editor)) { + $this->_editor->Value = $value; + } else { + parent::setValue($value); + } + } + + function getValue() { + if (isset($this->_editor)) { + return $this->_editor->Value; + } else { + return parent::getValue(); + } + } + + function setWidth($width) { + if (isset($this->_editor)) { + $this->_editor->Width = $width; + } + } + + function setHeight($height) { + if (isset($this->_editor)) { + $this->_editor->Height = $height; + } + } + + function toHtml() { + if (isset($this->_editor)) { + if ($this->_flagFrozen) { + return $this->getFrozenHtml(); + } else { + return $this->_getTabs() . $this->_editor->CreateHtml(); + } + } else { + return parent::toHtml(); + } + } + + function getFrozenHtml() { + $value = $this->getValue(); + $html = $value ."\n"; + return $html . $this->_getPersistantData(); + } +} diff --git a/lib/controller/inflector.php b/lib/controller/inflector.php new file mode 100644 index 0000000..f758b6a --- /dev/null +++ b/lib/controller/inflector.php @@ -0,0 +1,91 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class Inflector extends Object { + function __construct () { + parent::__construct(); + } + + /** + * Returns given $lower_case_and_underscored_word as a camelCased word. + * + * @param string $lower_case_and_underscored_word Word to camelize + * @return string Camelized word. likeThis. + */ + static function camelize($lower_case_and_underscored_word) { + return str_replace(" ","",ucwords(str_replace("_"," ",$lower_case_and_underscored_word))); + } + + /** + * Returns an underscore-syntaxed ($like_this_dear_reader) version of the $camel_cased_word. + * + * @param string $camel_cased_word Camel-cased word to be "underscorized" + * @return string Underscore-syntaxed version of the $camel_cased_word + */ + static function underscore($camel_cased_word) { + $camel_cased_word = preg_replace('/([A-Z]+)([A-Z])/','\1_\2', $camel_cased_word); + return strtolower(preg_replace('/([a-z])([A-Z])/','\1_\2', $camel_cased_word)); + } + + /** + * Returns a human-readable string from $lower_case_and_underscored_word, + * by replacing underscores with a space, and by upper-casing the initial characters. + * + * @param string $lower_case_and_underscored_word String to be made more readable + * @return string Human-readable string + */ + static function humanize($lower_case_and_underscored_word) { + return ucwords(str_replace("_"," ",$lower_case_and_underscored_word)); + } + + /** + * Returns corresponding table name for given $class_name. ("posts" for the model class "Post"). + * + * @param string $class_name Name of class to get database table name for + * @return string Name of the database table for given class + */ + static function tableize($class_name) { + return Inflector::underscore($class_name); + } + + /** + * Returns Cake model class name ("Post" for the database table "posts".) for given database table. + * + * @param string $tableName Name of database table to get class name for + * @return string + */ + static function classify($tableName) { + return Inflector::camelize($tableName); + } + + /** + * Returns $class_name in underscored form, with "_id" tacked on at the end. + * This is for use in dealing with foreign keys in the database. + * + * @param string $class_name + * @return string + */ + static function foreignKey($class_name) { + return Inflector::underscore($class_name) . "_id"; + } +} + +?> diff --git a/lib/mirror/ftp_mirror.php b/lib/mirror/ftp_mirror.php new file mode 100644 index 0000000..e0ef513 --- /dev/null +++ b/lib/mirror/ftp_mirror.php @@ -0,0 +1,110 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.16 + * @todo Delete folders. + */ + +class FtpMirror extends Object { + + function __construct() { + + } + + function connect() { + $this->connection = ftp_connect(MIRROR_HOSTNAME); + $this->login_result = ftp_login($this->connection, MIRROR_USERNAME, MIRROR_PASSWORD); + // Set it to a passive FTP connection. + ftp_pasv($this->connection, true); + // Check the connection. + if ((!$this->connection) || (!$this->login_result)) { + NDebug::debug('FTP Mirror connection has failed.' , N_DEBUGTYPE_INFO); + exit; + } else { + NDebug::debug('FTP Mirror connection was successful.' , N_DEBUGTYPE_INFO); + } + } + + function disconnect() { + if (ftp_quit($this->connection)) { + NDebug::debug('FTP Mirror disconnected.' , N_DEBUGTYPE_INFO); + } + } + + function putFile($filename) { + if ((!$this->connection) || (!$this->login_result)) { + $this->connect(); + } + $directories = dirname($filename); + $file = basename($filename); + $dir_array = explode('/', $directories); + $empty = array_shift($dir_array); + + // Change into MIRROR_REMOTE_DIR. + ftp_chdir($this->connection, MIRROR_REMOTE_DIR); + + // Create any folders that are needed. + foreach ($dir_array as $dir) { + // If it doesn't exist, create it. + // Then chdir to it. + if (@ftp_chdir($this->connection, $dir)) { + // Do nothing. + } else { + if (ftp_mkdir($this->connection, $dir)) { + ftp_chmod($this->connection, 0775, $dir); + ftp_chdir($this->connection, $dir); + } else { + NDebug::debug('Cannot create a folder via ftp.' , N_DEBUGTYPE_INFO); + } + } + } + + // Put the file into the folder. + $full_path = $_SERVER['DOCUMENT_ROOT'] . $filename; + if (ftp_put($this->connection, $file, $full_path, FTP_BINARY)) { + ftp_chmod($this->connection, 0775, $file); + NDebug::debug("FTP Mirror: $filename was uploaded successfully" , N_DEBUGTYPE_INFO); + } else { + NDebug::debug("FTP Mirror: $filename was NOT uploaded successfully" , N_DEBUGTYPE_INFO); + } + } + + function deleteFile($filename) { + if ((!$this->connection) || (!$this->login_result)) { + $this->connect(); + } + // Take off the leading / + $filename = eregi_replace('^/', '', $filename); + + // Change into MIRROR_REMOTE_DIR. + ftp_chdir($this->connection, MIRROR_REMOTE_DIR); + + if (ftp_delete($this->connection, $filename)) { + NDebug::debug("FTP Mirror: $filename WAS deleted successfully" , N_DEBUGTYPE_INFO); + } else { + NDebug::debug("FTP Mirror: $filename was NOT deleted successfully" , N_DEBUGTYPE_INFO); + } + } + + function synchronizeDirectory() { + + } + +} + +?> diff --git a/lib/mirror/rsync_mirror.php b/lib/mirror/rsync_mirror.php new file mode 100644 index 0000000..82644aa --- /dev/null +++ b/lib/mirror/rsync_mirror.php @@ -0,0 +1,96 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.16 + * @todo This doesn't work yet. + * @todo Need to create folder path in order for this to work. Patches welcome. + */ + +class RsyncMirror extends Object { + + + function __construct() { + $this->rsync_binary = '/usr/bin/rsync '; + + // Set SSH specific options as well as dealing with a public_key if available. + $this->private_key = CONF_DIR . "/ssh_private_key"; + + if (defined('MIRROR_RSYNC_SSH') && MIRROR_RSYNC_SSH) { + $this->options = '-v -e "ssh'; + if (file_exists($this->private_key)) { + $this->options .= ' -i ' . $this->private_key . '" '; + } else { + // Do nothing, needs a public key to work properly. + } + } + + // I think we need this - otherwise we can't be sure the permissions + // are good enough to read the file by the web server. + // $this->options .= '--chmod=a+rx -t -p '; + $this->options .= '-t -p -r '; + $this->exclude = '--exclude "CVS" --exclude ".svn" '; + $this->ssh_connection_details = MIRROR_USERNAME . '@' . MIRROR_HOSTNAME . ':' . MIRROR_REMOTE_DIR; + // TODO: Rsync connection details. + + } + + function connect() { + + } + + function disconnect() { + + } + + function putFile($filename) { + $full_path_filename = $_SERVER['DOCUMENT_ROOT'] . $filename; + if (defined('MIRROR_RSYNC_SSH') && MIRROR_RSYNC_SSH) { + $command = $this->rsync_binary . $this->options . $this->exclude . $full_path_filename . ' ' . $this->ssh_connection_details . eregi_replace('^/', '', $filename); + // TODO: This doesn't work yet - can't create all the directories from the top down. + exec($command, $output, $return_var); + } else { + // Set the password as an environment variable. + $this->password = MIRROR_PASSWORD; + putenv ("RSYNC_PASSWORD=$this->password"); + + // OR if you're using RSYNC without ssh, set the password in this file. + $this->password_file = CONF_DIR . "/rsync_password"; + + if (file_exists($this->password_file)) { + $this->options .= '--password-file=' . $this->password_file . ' '; + } + // Do an rsync specific command here. + } + } + + function deleteFile($filename) { + + } + + function synchronizeDirectory($directory=null) { + if (!$directory) { + $directory = $_SERVER['DOCUMENT_ROOT']; + } + // TODO: Synchronize the entire root directory. + } + +} + +?> diff --git a/lib/mirror/s3_mirror.php b/lib/mirror/s3_mirror.php new file mode 100644 index 0000000..d6fe7fb --- /dev/null +++ b/lib/mirror/s3_mirror.php @@ -0,0 +1,73 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.16 + */ + +class S3Mirror extends Object { + + function __construct() { + + } + + function connect() { + + } + + function disconnect() { + + } + + function putFile($filename) { + $s3svc = new S3(); + // Removing the first slash is important - otherwise the URL is different. + $aws_filename = eregi_replace('^/', '', $filename); + $filename = $_SERVER['DOCUMENT_ROOT'] . $filename; + $mime_type = NFilesystem::getMimeType($filename); + + // Read the file into memory. + $fh = fopen( $filename, 'rb' ); + $contents = fread( $fh, filesize( $filename ) ); + fclose( $fh ); + + $s3svc->putBucket( MIRROR_S3_BUCKET ); + $out = $s3svc->putObject( $aws_filename, $contents, MIRROR_S3_BUCKET, 'public-read', $mime_type ); + + // Now the file is accessable at: + // http://MIRROR_S3_BUCKET.s3.amazonaws.com/put/the/filename/here.jpg OR + // http://s3.amazonaws.com/MIRROR_S3_BUCKET/put/the/filename/here.jpg + + unset($s3svc); + } + + function deleteFile($filename) { + $s3svc = new S3(); + // Removing the first slash is important - otherwise the URL is different. + $aws_filename = eregi_replace('^/', '', $filename); + $s3svc->deleteObject($aws_filename, MIRROR_S3_BUCKET); + unset($s3svc); + } + + function synchronizeDirectory() { + + } + +} + +?> diff --git a/lib/model/fixtures.php b/lib/model/fixtures.php new file mode 100644 index 0000000..9160918 --- /dev/null +++ b/lib/model/fixtures.php @@ -0,0 +1,122 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class Fixtures { + var $loaded_fixtures = array(); + var $default_file_filter = '/\.ya?ml$/'; + var $all_loaded_fixtures = array(); + + var $connection = null; + var $table_name = null; + var $class_name = null; + var $file_filter = null; + var $fixture_path = array(); + + function Fixtures($connection, $table_name, $class_name, $fixture_path, $file_filter = null) { + $this->connection = &$connection; + $this->table_name = $table_name; + $this->fixture_path = $fixture_path; + $this->file_filter = $file_filter?$file_filter:$this->default_file_filter; + $this->class_name = $class_name?$class_name:Inflector::camelize($table_name); + + $this->_readFixtureFiles(); + } + + function createFixtures($fixture_path, $fixture_table_names, $fixture_class_names) { + $fixtures = array(); + $connection = &NDB::connect(); + $i = 0; + foreach ($fixture_table_names as $table_name) { + $fixtures[$table_name] = new Fixtures($connection, $table_name, $fixture_class_names[$i]?$fixture_class_names[$i]:null, $fixture_path . $table_name); + $i++; + } + // $this->all_loaded_fixtures = array_merge($this->all_loaded_fixtures, $fixtures_map); + foreach (array_reverse($fixtures) as $f) { + $f->deleteExistingFixtures(); + } + foreach ($fixtures as $f) { + $f->insertFixtures(); + } + return count($fixtures) > 1?$fixtures:$fixtures[0]; + } + + function deleteExistingFixtures($reset_pk = true) { + $this->connection->exec("DELETE FROM $this->table_name"); + } + + function insertFixtures() { + foreach ($this->loaded_fixtures as $fixture) { + $this->connection->exec("INSERT INTO {$this->table_name} (" . $fixture->keyList() . ") VALUES (" . $fixture->valueList() . ")"); + } + } + + function _readFixtureFiles() { + if (file_exists($yaml_file_path = $this->_yamlPath())) { + $yaml_string = file_get_contents($yaml_file_path); + include_once 'vendor/spyc.php'; + if ($yaml = Spyc::YAMLLoad($yaml_string)) { + foreach ($yaml as $name=>$data) { + $this->loaded_fixtures[$name] = new Fixture($data, $this->class_name); + } + } + } else if (file_exists($csv_file = $this->_csvPath())) { + // not supported yet + return false; + } + } + + function _yamlPath() { + return $this->fixture_path . '.yml'; + } + + function _csvPath() { + return $this->fixture_path . '.csv'; + } +} + +class Fixture { + var $fixture = array(); + var $class_name = null; + + function Fixture($fixture, $class_name) { + if (is_array($fixture)) { + $this->fixture = $fixture; + } else { + return false; + } + $this->class_name = $class_name; + } + + function find($class_name) { + return class_exists($class_name); + } + + function toHash() { + return $this->fixture; + } + + function keyList() { + return implode(', ', array_map(create_function('$k', '$db=&NDB::connect();return $db->quoteIdentifier($k);'), array_keys($this->fixture))); + } + + function valueList() { + return implode(', ', array_map(create_function('$k', '$db=&NDB::connect();return $db->quote($k);'), $this->fixture)); + } +} +?> diff --git a/lib/model/tree.php b/lib/model/tree.php new file mode 100644 index 0000000..44059a2 --- /dev/null +++ b/lib/model/tree.php @@ -0,0 +1,347 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class ModelTree extends NModel { + /** + * the root_node of the site architecture + * + * @access private + * @var string + */ + var $root_node = false; + + /** + * nodes array + * + * @access private + * @var array + */ + var $nodes = array(); + + /** + * Class Constructor + * + * the constructor of the class. sets the table and field variables + * as well as the root_node + * + * @access public + * @author Tim Glen + * @param int $id the table id to retrieve + * @param string $orderby table fields by which to order the result + * @return null + */ + function __construct() { + parent::__construct(); + if ((defined('TREE_CACHING') && TREE_CACHING == false) || false == ($nodes = NCache::getMenu())) { + $pk = $this->primaryKey(); + $model = clone($this); + $model->reset(); + if ($model->find()) { + while ($model->fetch()) { + $this->nodes[$model->$pk] = $model->toArray(); + } + } + unset($model); + if (defined('TREE_CACHING') && TREE_CACHING != false) { + NCache::createMenu($this->nodes); + } + } else { + $this->nodes =& $nodes; + } + } + + function getCache() { + return NCache::getMenu(); + } + + function createCache($con) { + return NCache::createMenu($con); + } + + function removeCache() { + return NCache::removeMenu(); + } + + /** + * getRootNode finds the root_node of the architecture + * + * @access public + * @author Tim Glen + * @return int table id of the root_node + * @see getParent() + */ + function getRootNode() { + if (!$this->root_node) { + $model = clone($this); + $model->reset(); + $table = $model->table(); + $parent_id = $table['parent_id']; + $conditions = 'parent_id' . ($parent_id & N_DAO_NOTNULL?'=0':' is NULL'); + if ($model->find(array('select'=>'id', 'conditions'=>$conditions), true)) { + $this->root_node = (int) $model->{$model->primaryKey()}; + } + unset($model); + } + return $this->root_node; + } + + /** + * isRootNode checks if an id is the root_node + * + * isRootNode + * + * @access public + * @author Tim Glen + * @param int $id the table id to test. + * @return boolean + * @see getParent() + */ + function isRootNode($id) { + return ($id == $this->getRootNode())?true:false; + } + + function getInfo($id) { + if ($id) { + return (isset($this->nodes[(int)$id]))?$this->nodes[(int)$id]:false; + } + return false; + } + + function getParent($id, $active=true, $visible=false) { + if ($id) { + if ($active && $this->nodes[$id]['active'] == 0) { + return false; + } else if ($visible && $this->nodes[$id]['visible'] == 0) { + return false; + } else { + return (isset($this->nodes[(int)$id]['parent_id']))?$this->nodes[(int)$id]['parent_id']:false; + } + } + return false; + } + + function getAncestors($id, $active=true, $visible=false, $ancestors=array()) { + $parent_id = $this->getParent($id, $active, $visible); + if ($parent_id != 0 && $parent_id != $id) { + $parent = $this->getInfo($parent_id); + if ($parent && (!$active || ($active && $parent['active'] != 0)) && (!$visible || ($visible && $parent['visible'] != 0))) { + $ancestors[] = $parent; + } + $ancestors = $this->getAncestors($parent_id, $active, $visible, $ancestors); + } + return $ancestors; + } + + function getChildren($id, $active=true, $visible=true) { + $id = $id?(int) $id:null; + $fields = $this->fields(); + $sql = 'SELECT ' . $this->primaryKey() . ' FROM ' . $this->__table; + $table = $this->table(); + $parent_id = $table['parent_id']; + $sql .= ' WHERE parent_id' . ($id?"=$id":($parent_id & N_DAO_NOTNULL?'=0':' is NULL')); + if ($active) { + $sql .= ' AND active != 0'; + } + if ($visible) { + $sql .= ' AND visible != 0'; + } + if (in_array('cms_deleted', $fields)) { + $sql .= ' AND cms_deleted=0'; + } + $sql .= ' ORDER BY sort_order, ' . $this->primaryKey(); + $res = &$this->query($sql); + if (PEAR::isError($res)) return false; + $children = array(); + while ($row = $res->fetchRow(MDB2_FETCHMODE_ORDERED)) { + $children[] = $this->getInfo($row[0]); + } + unset($res); + return $children; + } + + function getAllChildren($id, $active=true, $visible=true) { + $children = array(); + if (!$id) $id = 0; + $children = $this->getChildren($id, $active, $visible); + foreach ($children as $child) { + if ($this->isBranch($child['id'], $active, $visible)) { + $subchildren = $this->getAllChildren($child['id'], $active, $visible, $children); + $children = array_merge($children, $subchildren); + } + } + return $children; + } + + function getSiblings($id, $active=true, $visible=true) { + $pid = $this->getParent($id, $active, $visible); + $siblings = $this->getChildren($pid, $active, $visible); + + return $siblings; + } + + function isBranch($id, $active=true, $visible=false) { + if (count($this->getChildren($id, $active, $visible)) > 0) { + return true; + } else { + return false; + } + } + + function getNavAsList($page_id = false, $treat_title=false, $title_recurse=true) { + $menu = new Menu(); + if ($page_id == false) { + $page_id = $menu->getRootNode(); + } + if (CURRENT_SITE == 'admin') { + $checkactive = false; + $checkvisible = false; + } else { + $checkactive = true; + $checkvisible = true; + } + $main_nav = $menu->getChildren($page_id, $checkactive, $checkvisible); + $html = ''; + foreach($main_nav as $nav) { + $html .= "
      "; + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $nav['id']) $html .= ''; + $html .= Menu::getLink($nav, $treat_title); + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $nav['id']) $html .= ''; + $html .= "
      \n"; + if ($menu->isBranch($nav['id'], $checkactive, $checkactive)) { + $sub_treat_title = ($title_recurse == true)?$treat_title:false; + $html .= Menu::getChildList($menu, $nav['id'], $sub_treat_title); + } + } + return $html; + } + + function getChildList(&$menu, $page_id, $treat_title=false) { + if (CURRENT_SITE == 'admin') { + $checkactive = false; + $checkvisible = false; + } else { + $checkactive = true; + $checkvisible = true; + } + $children = $menu->getChildren($page_id, $checkactive, $checkvisible); + $html = ''; + $submenu = array(); + foreach ($children as $child) { + $html .= "\t
    • "; + $info = $menu->getInfo($child['id']); + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $info['id']) $html .= ''; + $html .= Menu::getLink($info, $treat_title); + if (isset($GLOBALS['page_id']) && $GLOBALS['page_id'] == $info['id']) $html .= ''; + if ($menu->isBranch($child['id'], $checkactive, $checkactive)) { + $html .= Menu::getChildList($menu, $child['id'], $treat_title); + } + $html .= "
    • \n"; + } + if ($html) { + $html = "
        \n" . $html . "
      \n\n"; + } + return $html; + } + + function getDirectories($url) { + $url = parse_url($url); + $path = $url['path']; + $path = preg_replace('/^\//', '', $path); + $path = preg_replace('/\/$/', '', $path); + $extensionpos = strrpos($path, '.'); + if ($extensionpos) { + $path = substr($path, 0, $extensionpos); + } + $path = str_replace('.' . DEFAULT_PAGE_EXTENSION, '', $path); + $path = preg_replace('/[\/]?index$/', '', $path); + $directories = explode('/', $path); + + return $directories; + } + + function IdToURL($id, $active=true, $visible=false, $url = '') { + if ($id != $this->getRootNode()) { + $info = $this->getInfo($id); + $filename = ($this->isBranch($id, $active, $visible))?$info['filename'] . '/':$info['filename'] . '.' . DEFAULT_PAGE_EXTENSION; + $url = $filename . $url; + $pid = $this->getParent($id, $active, $visible); + if ($pid && $this->getRootNode() != $pid) return $this->idToURL($pid, $active, $visible, $url); + } + return '/' . $url; + } + + function buildPath($id, $parent_id=0, $path='') { + if ($id != $this->getRootNode()) { + if ($path == '') { + $res = &$this->_db->query('SELECT id, filename, parent_id FROM page WHERE id=' . $id); + $row = $res->fetchRow(MDB2_FETCHMODE_ASSOC); + $res->free(); + $path .= '/' . $row['filename']; + $parent_id = $row['parent_id']?(int) $row['parent_id']:'NULL'; + } + $res = &$this->_db->query('SELECT id, parent_id, filename FROM page where id=' . $parent_id); + $info = $res->fetchRow(MDB2_FETCHMODE_ASSOC); + $res->free(); + if ($info['id'] != $this->getRootNode()) { + $path = '/' . $info['filename'] . $path; + $path = $this->buildPath($info['id'], $info['parent_id'], $path); + } else { + return $path; + } + } + return $path; + } + + function checkURL($url) { + if ($page = $this->URLToId($url)) { + return array('id', $page); + } + } + + function URLToId($url) { + $directories = $this->getDirectories($url); + $id = $this->getChildId($directories); + + return $id; + } + + function getChildId($directories, $parent_id = 0, $count = 0) { + if (!$parent_id) + $parent_id = $this->getRootNode(); + if ($count < sizeof($directories) && $directories[$count]) { + $children = $this->getChildren($parent_id, false, false); + foreach ($children as $id=>$child) { + if ($child['filename'] == $directories[$count] && $parent_id == $this->getParent($child['id'])) { + return $this->getChildId($directories, $child['id'], $count + 1); + } + } + return false; + } else { + return $parent_id; + } + } +} +?> diff --git a/lib/model/value_cast.php b/lib/model/value_cast.php new file mode 100644 index 0000000..23ca017 --- /dev/null +++ b/lib/model/value_cast.php @@ -0,0 +1,253 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class ValueCast extends NModel { + function castBoolean($val) { + return (bool) $val; + } + + function castInt($val) { + return (int) $val; + } + + function castReal($val) { + return (real) $val; + } + + function castStr($val) { + return (string) $val; + } + + function castDate($val) { + if ($val == is_int($val)) { // assume it's a timestamp + // do nothing + } else if (is_string($val)) { + $val = strtotime($val); + } else { + $val = null; + } + return date('Y-m-d', $val); + } + function castDateTime($val) { + if (is_int($val)) { // assume it's a timestamp + // do nothing + } else if (is_string($val)) { + $val = strtotime($val); + } else { + $val = null; + } + return date('Y-m-d H:i:s', $val); + } + function castTime($val) { + return $val; + } + function castTimestamp($val) { + if (is_int($val)) { // assume it's a timestamp + // do nothing + } else if (is_string($val)) { + $val = strtotime($val); + } + return $val; + } + + function prepBoolean($val, &$db) { + return $db->quoteSmart(ValueCast::castBoolean($val)?1:0); + } + + function prepInt($val, &$db) { + return $db->quoteSmart(ValueCast::castInt($val)); + } + + function prepReal($val, &$db) { + return $db->quoteSmart(ValueCast::castFloat($val)); + } + + function prepStr($val, &$db) { + // convert all utf-8 values to iso8859-1 + $val = ValueCast::toLatinISO($val); + return $db->quoteSmart(ValueCast::castStr($val)); + } + + function prepTimestamp($val, &$db) { + return ValueCast::castTimestamp($val); + } + function prepDate($val, &$db) { + return $db->quoteSmart(ValueCast::castDate($val)); + } + function prepDateTime($val, &$db) { + return $db->quoteSmart(ValueCast::castDateTime($val)); + } + function prepTime($val, &$db) { + return $db->quoteSmart(ValueCast::castTime($val)); + } + + function prepVals($vals, &$io) { + if (is_object($io) && is_a($io, 'InputOutput')) { + $table = $io->getObjectTable(); + $db = &$io->db; + } else if (is_string($io)) { + $table = $io; + include_once 'n_db.php'; + $db = &NDB::connect(); + } + $table_info = $db->tableInfo($table); + foreach ($vals as $field=>$val) { + $vals[$field] = ValueCast::prepVal($field, $val, $io, $table_info); + } + return $vals; + } + + function prepVal($field, $val, $config) { + // cast val + switch (true) { + case $def & N_DAO_DATE && $def & N_DAO_TIME: + $options = array('language'=>'en', 'format'=>'Y-m-d H:i', 'minYear'=>2000, 'maxYear'=>date('Y')+5); + $options = $this->getFieldOptions($field, $options); + $attributes = array(); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('date', $field, $element_label, $options, $attributes); + break; + case $def & N_DAO_DATE: + $options = array('language'=>'en', 'format'=>'Y-m-d', 'minYear'=>2000, 'maxYear'=>date('Y')+5); + $options = $this->getFieldOptions($field, $options); + $attributes = array(); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('date', $field, $element_label, $options, $attributes); + break; + case $def & N_DAO_TIME: + $options = array('language'=>'en', 'format'=>'H:i:s'); + $options = $this->getFieldOptions($field, $options); + $attributes = array(); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('date', $field, $element_label, $options, $attributes); + break; + case $def & N_DAO_INT: + $attributes = array(); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('text', $field, $element_label, $attributes); + break; + case $def & N_DAO_FLOAT: + $attributes = array(); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('text', $field, $element_label, $attributes); + break; + case $def & N_DAO_BOOL: + break; + case $def & N_DAO_TXT: + $attributes = array('rows'=>15, 'cols'=>50); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('textarea', $field, $element_label, $attributes); + break; + case $def & N_DAO_BLOB: + // do nothing here since binary fields shouldn't be displayed + break; + case $def & N_DAO_STR: + $attributes = array(); + $attributes = $this->getFieldAttributes($field, $attributes); + $form->addElement('text', $field, $element_label, $attributes); + break; + } + + // map form field types to db field types + switch ($field_type) { + case 'string': + case 'varchar': + case 'bpchar': + case 'longchar': + $val = ValueCast::prepStr($val, $db); + break; + case 'counter': + case 'int': + case 'integer': + $val = ValueCast::prepInt($val, $db); + break; + case 'bool': + case 'bit': + $val = ValueCast::prepBoolean($val, $db); + break; + case 'real': + case 'numeric': + case 'float4': + case 'float8': + $val = ValueCast::prepReal($val, $db); + break; + case 'timestamp': + $val = ValueCast::prepTimestamp($val, $db); + break; + case 'date': + $val = ValueCast::prepDate($val, $db); + break; + case 'datetime': + $val = ValueCast::prepDateTime($val, $db); + break; + case 'time': + $val = ValueCast::prepTime($val, $db); + break; + case 'year': + $val = ValueCast::prepInt($val, $db); + break; + case 'blob': + case 'text': + case 'longbinary': + $val = ValueCast::prepStr($val, $db); + break; + default: + $val = ValueCast::prepStr($val, $db); + } + return $val; + } + + // utf-8 handling since PHP doesn't have it :( + function toLatinISO($str) { + return ValueCast::unicodeToIsoEntity(ValueCast::utf8ToUnicode($str)); + } + + function utf8ToUnicode($str) { + $ret = array(); + $values = array(); + $lookingFor = 1; + for ($i=0;$i 127)?'&#' . $char . ';':chr($char); + } + return $ret; + } +} +?> diff --git a/lib/n_asset.php b/lib/n_asset.php new file mode 100644 index 0000000..236c2fb --- /dev/null +++ b/lib/n_asset.php @@ -0,0 +1,192 @@ +key = urlencode($filename); + + $this->filename = str_replace(array('.css','.js'), '', ltrim($filename, '/')); + + $this->filename_trim('&'); + $this->filename_options('nocache', 'nocache'); + $this->filename_trim('?'); + $this->filename_options('minify', '.min'); + + $this->cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>60*24*24)); + } + + /** + * Prints a cached version of a compiled asset or compiles the source + * and returns that. + */ + function render(){ + + // Determine what kind of asset is being requested + $handler = $this->handler($this->filename); + + // Set the proper content-type in the headers + header('Content-Type: ' . $this->content_type($handler)); + + $content = null; + + // Look for the compiled asset in the cache + if (!$this->nocache) $content = $this->get($this->key); + + if (!$content){ + // Compile the asset + $content = $this->compile($this->filename, $handler); + if (!$this->nocache) $this->set($this->key, $content); + } + + // Send it out! + echo $content; + } + + /** + * Examines the file extension and determines which handler to call + */ + function handler($filename='') { + switch (end(explode('.',$filename))) { + case "less": return "less"; + case "coffee": return "coffee"; + default: return "no_handler"; + } + } + + /** + * Examines the handler and determines which content-type to use + */ + function content_type($handler='') { + switch ($handler) { + case "less": return "text/css"; + case "coffee": return "text/javascript"; + default: return "text/plain"; + } + } + + /** + * Returns a compiled less or coffee-script file + */ + function compile($filename='', $handler=''){ + + // Check if file can be found + if (! is_readable(DOCUMENT_ROOT.'/'.$filename)) $handler = "no_file"; + + // Return the compiled web asset + switch ($handler) { + + case "less": + if ($this->minify) + $cmd = 'lessc --yui-compress ' . escapeshellarg($filename); + else + $cmd = 'lessc ' . escapeshellarg($filename); + return $this->process($cmd); + + case "coffee": + $tmp_file = '/tmp/' . basename($filename) . '.js'; + if ($this->minify) + $cmd = 'importer ' . escapeshellarg($filename) . " $tmp_file; uglifyjs $tmp_file"; + else + $cmd = 'importer ' . escapeshellarg($filename) . " $tmp_file; cat $tmp_file"; + return $this->process($cmd); + + case "no_file": + return "/* error: cannot find file {$filename} */"; + + case "no_handler": + default: + return "/* error: no handler found for {$filename} */"; + } + } + + /** + * Similar to exec(), except it appends stderr to stdout on error + * also sets cwd to the public directory + */ + function process($command){ + $descriptorspec = array( + 0 => array("pipe", "r"), // stdin + 1 => array("pipe", "w"), // stdout + 2 => array("pipe", "w"), // stderr + ); + $process = proc_open($command, $descriptorspec, $pipes, DOCUMENT_ROOT, null); + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + + if ($stderr) $stdout .= "/* error: {$stderr} */"; + return $stdout; + } + + /** + * Get a cached record, or not. + * + * @param string $key The key for the record, pick your schema + * @param int $ttl The time-to-live for cached versions, in minutes (default=5) + * @return mixed The data, or false if the record is expired or doesn't exist + */ + function get($key){ + if ($data = $this->cache->get($key, $this->cache_group)){ + return unserialize($data); + } + return false; + } + + /** + * Set a key/value pair + * + * @param string $key + * @param string $value + */ + function set($key, $value){ + return $this->cache->save(serialize($value), $key, $this->cache_group); + } + + /** + * set options based on value at end of filename + * + * @param string $option + * @param string $flag + */ + function filename_options($option, $flag){ + $filename = $this->filename; + + $flag_length = strlen($flag); + $filename_length = strlen($filename); + + $filename_end = substr($filename, $filename_length - $flag_length); + if ($filename_end == $flag) { + $this->$option = true; + $filename = substr($filename, 0, ($flag_length * -1)); + } + + $this->filename = $filename; + } + + /** + * trim the filename after a string + * + * @param string $string + */ + function filename_trim($string){ + $this->filename = array_shift(explode($string, $this->filename)); + } + +} +?> diff --git a/lib/n_auth.php b/lib/n_auth.php new file mode 100644 index 0000000..9253fbb --- /dev/null +++ b/lib/n_auth.php @@ -0,0 +1,110 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NAuth extends Auth { + function __construct() { + $db = &NDB::connect(); + $options['table'] = 'cms_auth'; + $options['usernamecol'] = 'username'; + $options['passwordcol'] = 'password'; + $options['dsn'] = &$db; + $options['db_fields'] = 'id, real_name, user_level, status'; + $options['cryptType'] = 'md5'; + $options['db_options'] = array(); + + parent::Auth('MDB2', $options, array($this, 'showLogin')); + $this->setLoginCallback($this, 'loginCallBack'); + $this->setLogoutCallback($this, 'loginCallBack'); + if (!$this->checkAuth()) { + $this->start(); + } + if (isset($_GET['logout']) && $_GET['logout']) { + $this->logout(); + $this->start(); + exit; + } + } + function NAuth() { + $this->__construct(); + } + + function showLogin($username, $status, &$auth) { + $path = '/' . APP_DIR . '/login'; + if (!preg_match('|^' . $path. '|', $_SERVER['REQUEST_URI'])) { + $referer = str_replace('?' . $_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']); + $qs = ''; + foreach ($_GET as $k=>$v) { + if ($k != 'logout') + $qs .= ($qs?'&':'?') . "$k=$v"; + } + $referer .= $qs; + header('Location:' . $path . '?_referer=' . urlencode($referer)); + exit; + } + } + + function loginCallBack($username, &$auth) { + + } + + function logoutCallBack($username, &$auth) { + + } + + function statusMessage($status) { + // AUTH_IDLED, AUTH_EXPIRED, AUTH_WRONG_LOGIN, AUTH_METHOD_NOT_SUPPORTED, AUTH_SECURITY_BREACH + switch ($status) { + case AUTH_IDLED: + $status_msg = 'You were idle for too long and your session was automatically reset.'; + break; + case AUTH_EXPIRED: + $status_msg = 'Your session has expired.'; + break; + case AUTH_WRONG_LOGIN: + $status_msg = 'Either your username or password were incorrect. Please check your information and try again.'; + break; + default: + $status_msg = 'Something went wrong. Please login again.'; + break; + } + return $status_msg; + } + + function currentUserID() { + $user_id = (int) $this->getAuthData('id'); + return $user_id?$user_id:0; + } +} +?> \ No newline at end of file diff --git a/lib/n_cache.php b/lib/n_cache.php new file mode 100755 index 0000000..f7d6dda --- /dev/null +++ b/lib/n_cache.php @@ -0,0 +1,84 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NCache { + function createMenu($content) { + $cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>3600*24*7)); + return $cache->save(serialize($content), 'menunodes', 'default'); + } + function getMenu() { + $cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>3600*24*7)); + $nodes = $cache->get('menunodes', 'default'); + if (!$nodes) return; + return unserialize($nodes); + } + function removeMenu() { + $cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>3600*24*7)); + return $cache->remove('menunodes', 'default'); + } + + function createTreeAsSelect($content) { + $cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>3600*24*7)); + return $cache->save(serialize($content), 'treeasselect', 'default'); + } + function getTreeAsSelect() { + $cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>3600*24*7)); + $nodes = $cache->get('treeasselect', 'default'); + if (!$nodes) return; + return unserialize($nodes); + } + function removeTreeAsSelect() { + $cache = new Cache_Lite(array('cacheDir'=>CACHE_DIR . '/ntercache/', 'lifeTime'=>3600*24*7)); + return $cache->remove('treeasselect', 'default'); + } + + function removeJavascript($id=0) { + $page = &NController::singleton('page'); + $page->base_view_dir = ROOT_DIR; + $page->view_caching = (bool) PAGE_CACHING; + $page->view_cache_lifetime = JS_CACHE_LIFETIME; + $view_options = array('action'=>'blank'); + // REMOVE JAVASCRIPT CACHES + $javascript_caches = array('javascript', 'javascript_secure', 'javascript_qualified', 'admin_javascript', 'admin_edit_javascript'); + foreach ($javascript_caches as $javascript_cache) { + $page->view_cache_name = $javascript_cache; + $view = &NView::singleton($page); + $title = ucfirst(str_replace('_', ' ', $javascript_cache)); + if ($view->isCached($view_options)) { + $cache_cleared = $view->clearCache($view_options); + if ($cache_cleared) { + NDebug::debug($title . ' cache removed due to page edit on Page ID ' . $id . '.', N_DEBUGTYPE_CACHE); + } else { + NDebug::debug($title . ' cache failed attempted removal due to page edit on Page ID ' . $id . '.', N_DEBUGTYPE_CACHE); + } + } else { + NDebug::debug($title . ' cache failed attempted removal due to page edit on Page ID ' . $id . ' but cache does not exist.', N_DEBUGTYPE_CACHE); + } + } + unset($view); + } +} +?> diff --git a/lib/n_controller.php b/lib/n_controller.php new file mode 100644 index 0000000..c6a0c7d --- /dev/null +++ b/lib/n_controller.php @@ -0,0 +1,648 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NController extends Object { + /** + * Container for a NAuth object + * + * @var object + * @access private + */ + var $_auth = null; + /** + * Container for an NFlash object + * + * @var object + * @access public + */ + var $flash = null; + /** + * Controller name + * + * Ought to be an_underscored_name + * + * @var string + * @access public + */ + var $name = null; + /** + * Array of models used in the controller + * + * @var array + * @access public + */ + var $models = array(); + /** + * Current action/method being called + * + * @var string + * @access public + */ + var $action = null; + /** + * Url currently being called + * + * @var string + * @access public + */ + var $url = null; + /** + * $_GET|$_POST variables currently loaded + * + * @var string + * @access public + */ + var $params = null; + /** + * Base dir for the controller + * + * Where the controller is to be found relative to app/controllers/ + * + * @var string + * @access public + */ + var $base_dir = null; + + // view settings + /** + * Controller title to be displayed in the view + * + * @var string + * @access public + */ + var $page_title = null; + /** + * Whether to render immediately or not + * + * @var boolean + * @access public + */ + var $auto_render = true; + /** + * Base dir for the controllers views + * + * Where the views to be found relative to app/views/ + * + * @var string + * @access public + */ + var $base_view_dir = null; + /** + * Contains all variables to be passed to the view + * + * @see NController::set(), NController::setAppend(), NController::setPrepend() + * @var array + * @access private + */ + var $_view_assigns = array(); + /** + * View caching + * + * @var boolean + * @access public + */ + var $view_caching = false; + /** + * View cache lifetime + * + * @var int + * @access public + */ + var $view_cache_lifetime = -1; // -1 == forever + /** + * View cache name + * + * @var string + * @access public + */ + var $view_cache_name = null; + /** + * List of fields to display in the view + * + * @var array + * @access public + */ + var $display_fields = array(); + + // notice is passed to the VIEW for layouts + /** + * Special variable ot be sent to view Layouts + * + * @var string + * @access public + */ + var $notice = ''; + + // filters + // TODO: implement filters + var $_before_filters = array(); + var $_after_filters = array(); + + function __construct() { + $this->viewPath = Inflector::underscore($this->name); + $this->modelClass = $this->name; + $this->modelKey = Inflector::underscore($this->modelClass); + if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') { + $this->params = array_merge($_GET, $_POST); + foreach ($this->params as $key=>$param) { + if (is_string($param)) { + $this->params[$key] = urldecode($param); + } + } + } else { + $this->params = $_GET; + } + $this->page_title = $this->page_title?$this->page_title:Inflector::humanize($this->name); + $this->flash = &NFlash::singleton(); + parent::__construct(); + } + + function __destruct() { + unset($this->_auth); + foreach ($this->models as $model) { + unset($model); + } + } + + /** + * Factory pattern to return the named controller + * + * @param string $controller - should be an underscored word + * @param array $params - paramaters to pass to the controller factory + * @return object + */ + static function &factory($controller, $params = null) { + $ret = false; + $file = NController::getIncludePath($controller); + if (!$file) { + NController::debug("Controller file not found for '$controller'", N_DEBUGTYPE_ASSET, PEAR_LOG_ERR); + return $ret; + } + include_once $file; + $class = NController::getClassName($controller); + if (class_exists($class)) { + $ret = new $class($params); + } + return $ret; + } + + /** + * Singleton pattern to return the same controller from wherever it is called + * + * @param string $controller - should be an underscored word + * @param array $params - paramaters to pass to the controller factory + * @see NController::factory + * @return object + */ + static function &singleton($controller, $params = null) { + static $controllers; + if (!isset($controllers)) $controllers = array(); + $key = md5($controller); + if (!isset($controllers[$key])) { + $controllers[$key] = &NController::factory($controller, $params); + } + return $controllers[$key]; + } + + /** + * Checks to see if a controller exists + * + * @param string $controller - should be an underscored word + * @return string + */ + static function exists($controller) { + if (!$controller) return false; + $path = NController::getIncludePath($controller); + if (!$path) return false; + include_once $path; + $class = NController::getClassName($controller); + return class_exists($class); + } + + /** + * Finds the path where the controller lives + * + * @param string $controller - should be an underscored word + * @return string + */ + static function getIncludePath($controller) { + $file = false; + $path = '%s/app/controllers/%s_controller.php'; + if (file_exists(sprintf($path, ROOT_DIR, $controller))) { + return sprintf($path, ROOT_DIR, $controller); + } else if (file_exists(sprintf($path, BASE_DIR, $controller))) { + return sprintf($path, BASE_DIR, $controller); + } else if (preg_match('/^' . APP_DIR . '/', $controller)) { + // TODO:: fix this piece of hackery. We need to load the controllers based on Routes rather than manually checking for 'nterchange' + if (APP_DIR == $controller) { + return sprintf($path, BASE_DIR, 'nterchange/' . $controller); + } else { + $controller_file = preg_replace('/^' . APP_DIR . '_/', '', $controller); + if (file_exists(sprintf($path, BASE_DIR, 'nterchange/' . $controller_file))) { + return sprintf($path, BASE_DIR, 'nterchange/' . $controller_file); + } + } + return false; + } + return false; + } + + /** + * Changes the controller name to our standard Controller Class naming convention + * + * @param string $controller - should be an underscored word + * @return string + */ + static function getClassName($controller) { + return Inflector::camelize($controller) . 'Controller'; + } + + /** + * Redirects the browser to another action in the same controller + * + * @param string|array $action - should be an underscored word (same as url) or an array of controller/action + * @param unknown_type $parameter - optional parameter which is likely an int + * @return null + */ + function redirectTo($action, $parameter=null, $additional_params=array()) { + include_once 'view/helpers/url_helper.php'; + $url_params = array(); + if (is_array($action)) { + $url_params['controller'] = $action[0]; + $url_params['action'] = isset($action[1])?$action[1]:($parameter?'index':''); + } else { + $url_params['action'] = $action; + // If there's a starting slash - assume it's a direct URL. + if (eregi('^/', $url_params['action'])) { + header('Location:' . $url_params['action']); + exit; + } + } + $url_params['id'] = $parameter; + $url = urlHelper::urlFor($this, array_merge($url_params, array_merge($_GET, $additional_params))); + $url = html_entity_decode($url); + header('Location:' . $url); + exit; + } + + function getParam($param) { + if (isset($this->params[$param])) { + return $this->params[$param]; + } + return false; + } + + function setParam($param, $value) { + $this->params[$param] = $value; + return; + } + + /** + * Gets an instance of the view object & checks the cache based on the + * options passed. + * + * @param array $options + * @return mixed + */ + function isCached($options=array()) { + // check for a view cache + $view = &NView::singleton($this); + return $view->isCached($options); + } + + /** + * Gets an instance of the view object & checks the cache of the + * passed layout + * + * @param array $options + * @return mixed + */ + function isCachedLayout($layout) { + // check for a view cache + $view = &NView::singleton($this); + return $view->isCachedLayout($layout); + } + + /** + * Empty stub method - really meant to be used in a particular controller. + * This is called just before things are actually rendered - so you can set + * additional variables and/or pull in additional data that's not normally + * in a view without resorting to helper files. Put your own beforeRender + * method in a particular controller and do your own magic there. + * + * @param null + * @return null + */ + function beforeRender() { + // Nothing doing here - all the magic happens elsewhere. + } + + /** + * Gets an instance of the view object & prepares it for rendering the output, then + * asks the view to actually do the job. + * + * @param array $options + * @return mixed + */ + function render($options=array()) { + $view = &NView::singleton($this); + $this->beforeRender(); + return $view->render($options); + } + + /** + * Gets an instance of the view object & prepares it for rendering a layout, then + * asks the view to actually do the job. + * + * @param string $layout - the layout file to use + * @param string $main_content + * @param string $sidebar_content + * @param boolean $return - whether to return the render or print it immediately + * @return mixed + */ + function renderLayout($layout, $main_content, $sidebar_content=null, $return=false) { + $view = &NView::singleton($this); + return $view->renderLayout($layout, $main_content, $sidebar_content, $return); + } + + /** + * Unsets a variable or multiple variables to be used in the View layer + * + * @param mixed $var - can be a string or an array + * @param mixed $val - if included, is used as the value of $var + * @return null + */ + function deset($var) { + if ($var == '*') { + $this->_view_assigns = array(); + return; + } + if (is_string($var)) { + if (isset($this->_view_assigns[$var])) unset($this->_view_assigns[$var]); + } else if (is_array($var)) { + foreach ($var as $key) { + if (isset($this->_view_assigns[$key])) unset($this->_view_assigns[$key]); + } + } + } + + /** + * Sets a variable or multiple variables to be used in the View layer + * + * @param mixed $var - can be a string or an array + * @param mixed $val - if included, is used as the value of $var + * @return null + */ + function set($var, $val=null) { + if (is_string($var) && !is_null($val)) { + $this->_view_assigns[$var] = $val; + } else if (is_array($var)) { + foreach ($var as $key=>$val) { + $this->_view_assigns[$key] = $val; + } + } + } + + /** + * Appends to a previously set variable or multiple variables to be used in the View layer + * + * @param mixed $var - can be a string or an array + * @param mixed $val - if included, is used as the value of $var + * @return null + */ + function setAppend($var, $val=null) { + if (is_string($var) && $val) { + if (!isset($this->_view_assigns[$var])) $this->_view_assigns[$var] = null; + if (is_array($val)) { + $this->_view_assigns[$var] = array_merge($this->_view_assigns[$var], $val); + } else { + $this->_view_assigns[$var] .= $val; + } + } else if (is_array($var)) { + foreach ($var as $key=>$val) { + if (!isset($this->_view_assigns[$key])) $this->_view_assigns[$key] = null; + if (is_array($val)) { + $this->_view_assigns[$key] = array_merge($this->_view_assigns[$key], $val); + } else { + $this->_view_assigns[$key] .= $val; + } + } + } + } + + /** + * Prepends to a previously set variable or multiple variables to be used in the View layer + * + * @param mixed $var - can be a string or an array + * @param mixed $val - if included, is used as the value of $var + * @return null + */ + function setPrepend($var, $val=null) { + if (is_string($var) && $val) { + if (!isset($this->_view_assigns[$var])) $this->_view_assigns[$var] = ''; + $this->_view_assigns[$var] = $val . $this->_view_assigns[$var]; + } else if (is_array($var)) { + foreach ($var as $key=>$val) { + if (!isset($this->_view_assigns[$key])) $this->_view_assigns[$key] = ''; + $this->_view_assigns[$key] = $val . $this->_view_assigns[$key]; + } + } + } + + // model functions + /** + * Returns a reference to the model with the same name as the controller, if it exists + * + * @see NController:loadModel(); + * @return object + */ + function &getDefaultModel() { + $model = &$this->loadModel($this->name); + return $model; + } + + /** + * Loads references to the model(s) that is/are passed into the + * $this->models array + * + * @see NController:loadModel(); + * @return array Hash of all loaded models + */ + function &loadModels($models) { + if (is_string($models)) { + $this->loadModel($models); + } else if (is_array($models)) { + foreach ($models as $model) { + $this->loadModel($model); + } + } + return $this->models; + } + + /** + * Loads a reference to the model that is passed into the $this->models + * array and returns it + * + * @see NModel:factory(); + * @return object + */ + function &loadModel($model) { + if (!isset($this->models[$model])) { + $this->models[$model] = &NModel::factory($model); + if ($this->models[$model] == false) { + unset($this->models[$model]); + $ret = false; + return $ret; + } + } + return $this->models[$model]; + } + + + // Form-specific methods - to be optionally overridden in subclasses + function preGenerateForm() { + } + function postGenerateForm(&$form) { + $model = &$this->loadModel($this->name); + if (isset($model->bitmask_fields) && is_array($model->bitmask_fields) && count($model->bitmask_fields)) { + foreach($model->bitmask_fields as $field=>$bitmask_array ) { + if (isset($model->{$model->primaryKey()})) { + $form_group = &$form->getElement($field); + $form_elements = &$form_group->getElements(); + foreach ($form_elements as $key=>$form_element) { + $bit = $form_element->getName(); + $form_elements[$key]->setChecked($bit & $model->$field?true:false); + } + } + } + } + } + function preProcessForm(&$values) { + $model = &$this->loadModel($this->name); + if (isset($model->bitmask_fields) && is_array($model->bitmask_fields) && count($model->bitmask_fields)) { + foreach($model->bitmask_fields as $field=>$bitmask_array ) { + if (isset($values[$field]) && is_array($values[$field])) { + $bitmask_total = 0; + foreach ($values[$field] as $bit=>$foo) { + $bitmask_total+=$bit; + } + $values[$field] = $bitmask_total; + } else { + $values[$field] = 0; + } + } + } + } + function postProcessForm(&$values) { + } + + // Filter functionality + // TODO: build in filter functionality + function beforeFilter($call, $params=null) { + if (is_callable($call)) { + $this->_before_filters[] = array($call, $params); + return true; + } + return false; + } + + function prependBeforeFilter($call, $params=null) { + if (is_callable($call)) { + array_unshift($this->_before_filters, array($call, $params)); + return true; + } + return false; + } + + function afterFilter($call, $params=null) { + if (is_callable($call)) { + $this->_after_filters[] = array($call, $params); + return true; + } + return false; + } + + function prependAfterFilter($call, $params=null) { + if (is_callable($call)) { + array_unshift($this->_after_filters, array($call, $params)); + return true; + } + return false; + } + + // Methods to flatten or push nested objects to a static array + function collectionToArray($collection){ + $arr = array(); + foreach ($collection as $object) { + $arr[] = $object->toArray(); + } + return $arr; + } + + // Aliased method for collectionToArray + function flatten($collection){ + return $this->collectionToArray($collection); + } + + + + + + // utility functions + function debug($message, $debug_type = N_DEBUGTYPE_INFO, $log_level = PEAR_LOG_DEBUG, $ident=false) { + if (!$ident) { + $ident = (isset($this) && is_a($this, __CLASS__))?get_class($this):__CLASS__; + } + NDebug::debug($message, $debug_type, $log_level, $ident); + } +} +?> diff --git a/lib/n_date.php b/lib/n_date.php new file mode 100644 index 0000000..11402cd --- /dev/null +++ b/lib/n_date.php @@ -0,0 +1,280 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NDate extends Object { + function &getClientTZ() { + // if SITE_TIME_ZONE is present, use it, otherwise, best guess as to server's local time zone + if (defined('SITE_TIME_ZONE') && Date_TimeZone::isValidID(SITE_TIME_ZONE)) { + $client_tz = new Date_TimeZone(SITE_TIME_ZONE); + } else { + $client_tz = &Date_TimeZone::getDefault(); + } + return $client_tz; + } + + function convertTimeToClient($value, $format='') { + if (is_object($value) && is_a($value, 'Date')) { + $dateobj = &$value; + } else if (is_string($value)) { + // if the field is date/time, not_null and is "empty", then nullify it + if ((preg_match('/^00:00(?::00)?/', $value)) || preg_match('/^0000-00-00 00:00(?::00)?/', $value)) { + $value = null; + } + } + if ($value) { + if (!$format) { + switch (1==1) { + case preg_match('/^\d\d\d\d-\d\d-\d\d \d\d:\d\d(?::\d\d)?$/', $value): + $format = '%Y-%m-%d %H:%M:%S'; + break; + case preg_match('/^\d\d\d\d-\d\d-\d\d$/', $value): + $format = '%Y-%m-%d'; + break; + case preg_match('/^\d\d:\d\d(?::\d\d)?$/', $value): + $format = '%H:%M:%S'; + break; + default: + $format = '%Y-%m-%d %H:%M:%S'; + } + } + if (!isset($dateobj)) { + $dateobj = new Date($value); + $dateobj->setTZbyID('UTC'); + } + $dateobj->convertTZ(NDate::getClientTZ()); + if (is_int($format)) { + $value = $dateobj->format($format); + } else { + $value = $dateobj->format($format); + } + unset($dateobj); + } + return $value; + } + + function convertTimeToUTC($value, $format='') { + // if the field is date/time and is "empty", then it's false + if ((preg_match('/^00:00(?::00)?/', $value)) || preg_match('/^0000-00-00 00:00(?::00)?/', $value)) { + $value = false; + } + // no SITE_TIME_ZONE is specified + if ($value) { + $utc_tz = new Date_TimeZone('UTC'); + $client_tz = &NDate::getClientTZ(); + // if no format is specified, then it's a best guess + if (!$format) { + switch (1==1) { + case preg_match('/^\d\d\d\d-\d\d-\d\d \d\d:\d\d(?::\d\d)?$/', $value): + $format = '%Y-%m-%d %H:%M:%S'; + break; + case preg_match('/^\d\d\d\d-\d\d-\d\d$/', $value): + $format = '%Y-%m-%d'; + break; + case preg_match('/^\d\d:\d\d(?::\d\d)?$/', $value): + $format = '%H:%M:%S'; + break; + default: + $format = '%Y-%m-%d %H:%M:%S'; + } + } + $dateobj = new Date($value); + $dateobj->setTZ($client_tz); + $dateobj->convertTZ($utc_tz); + // if it's an int, then assume it's a DATE_FORMAT constant + if (is_int($format)) { + $value = $dateobj->getDate($format); + } else { + $value = $dateobj->format($format); + } + unset($server_tz); + unset($client_tz); + unset($dateobj); + } + return $value; + } + + function validDateTime($value) { + if ((preg_match('/^00:00(?::00)?/', $value)) || preg_match('/^0000-00-00$/', $value) || preg_match('/^0000-00-00 00:00(?::00)?/', $value)) { + return false; + } else if ((preg_match('/^\d\d:\d\d(?::\d\d)?/', $value)) || preg_match('/^\d\d\d\d-\d\d-\d\d$/', $value) || preg_match('/^\d\d\d\d-\d\d-\d\d \d\d:\d\d(?::\d\d)?/', $value)) { + return true; + } + return false; + } + + function now($format = DATE_FORMAT_ISO) { + // this will force to the server time regardless of whether date() is returning UTC time or not + $dateobj = new Date(gmdate('Y-m-d H:i:s')); + $dateobj->setTZbyID('UTC'); + if (is_int($format)) { + $date = $dateobj->getDate($format); + } else if (is_string($format)) { + $date = $dateobj->format($format); + } + unset($dateobj); + return $date; + } + + function dateToArray($date) { + if (empty($date)) { + $date = array(); + } elseif (is_scalar($date)) { + if (!is_numeric($date)) { + $value = strtotime($date); + } + // fill all possible values + $arr = explode('-', date('w-d-n-Y-h-H-i-s-a-A-W', (int)$date)); + $date = array( + 'D' => $arr[0], + 'l' => $arr[0], + 'd' => $arr[1], + 'M' => $arr[2], + 'm' => $arr[2], + 'F' => $arr[2], + 'Y' => $arr[3], + 'y' => $arr[3], + 'h' => $arr[4], + 'g' => $arr[4], + 'H' => $arr[5], + 'i' => $arr[6], + 's' => $arr[7], + 'a' => $arr[8], + 'A' => $arr[9], + 'W' => $arr[10] + ); + } + return $date; + } + + function arrayToDate($date_input, $timestamp = false) { + // possible year values + if (isset($date_input['Y'])) { + $year = $date_input['Y']; + } else if (isset($date_input['y'])) { + $year = $date_input['y']; + } + // possible month values + if (isset($date_input['F'])) { + $month = $date_input['F']; + } else if (isset($date_input['m'])) { + $month = $date_input['m']; + } else if (isset($date_input['M'])) { + $month = $date_input['M']; + } else if (isset($date_input['n'])) { + $month = $date_input['n']; + } + // possible day values + if (isset($date_input['d'])) { + $day = $date_input['d']; + } else if (isset($date_input['j'])) { + $day = $date_input['j']; + } + // possible hour values + if (isset($date_input['g'])) { + $hour = $date_input['g']; + } else if (isset($date_input['h'])) { + $hour = $date_input['h']; + } else if (isset($date_input['G'])) { + $hour = $date_input['G']; + } else if (isset($date_input['H'])) { + $hour = $date_input['H']; + } + // possible am/pm values + if (isset($date_input['a'])) { + $ampm = $date_input['a']; + } else if (isset($date_input['A'])) { + $ampm = $date_input['A']; + } + // instantiate date object + $datestr = ''; + if (isset($year) || isset($month) || isset($day)) { + if (isset($year) && !empty($year)) { + if (strlen($year) < 2) { + $year = '0' . $year; + } + if (strlen($year) < 4) { + $year = substr($year, 0, 2) . $year; + } + } else { + $year = '0000'; + } + if ($year != '0000' && isset($month) && !empty($month)) { + if (strlen($month) < 2) { + $month = '0' . $month; + } + } else { + $month = '00'; + } + if ($year != '0000' && $month != '00' && isset($day) && !empty($day)) { + if (strlen($day) < 2) { + $day = '0' . $day; + } + } else { + $day = '00'; + } + $datestr .= "$year-$month-$day"; + } + if (isset($hour) || isset($date_input['i']) || isset($date_input['s'])) { + // set the hour + if (isset($hour) && !empty($hour)) { + if (strlen($hour) < 2) { + $hour = '0' . $hour; + } + } else { + $hour = '00'; + } + if (isset($ampm)) { + if (strtolower($ampm) == 'pm' && strlen($hour) == 1) { + $hour += 12; + } + } + // set the minutes + if (isset($date_input['i']) && !empty($date_input['i'])) { + if (strlen($date_input['i']) < 2) { + $date_input['i'] = '0' . $date_input['i']; + } + } else { + $date_input['i'] = '00'; + } + $datestr .= ($datestr != ''?' ':'') . "$hour:{$date_input['i']}"; + // set the seconds + if (isset($date_input['s']) && !empty($date_input['s'])) { + $datestr .= ':' . (strlen($date_input['s']) < 2?'0':'') . $date_input['s']; + } else { + $datestr .= ':00'; + } + } + // feed it into the date object + $dateobj = new Date($datestr); + // set the time zone + $dateobj->setTZ(NDate::getClientTZ()); + // pull the string back out + $datestr = $dateobj->getDate(); + unset($dateobj); + return $datestr; + } +} +?> diff --git a/lib/n_db.php b/lib/n_db.php new file mode 100755 index 0000000..f686017 --- /dev/null +++ b/lib/n_db.php @@ -0,0 +1,116 @@ +, Andy VanEe + * @copyright 2003-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NDB { + + static $type = 'mysql'; + static $username = 'root'; + static $password = false; + static $server = 'localhost'; + static $db = 'nterchange'; + + static function serverDSN() { + $type = getenv('DB_TYPE'); + $username = getenv('DB_SERVER_USERNAME'); + $password = getenv('DB_SERVER_PASSWORD'); + $server = getenv('DB_SERVER'); + $type = $type ? $type : self::$type; + $username = $username ? $username : self::$username; + $password = $password ? $password : self::$password; + $password = $password ? ':'.$password : ''; + $server = $server ? $server : self::$server; + + return sprintf("%s://%s%s@%s", $type, $username, $password, $server); + } + + static function dsn() { + $db = getenv('DB_DATABASE'); + $db = $db ? $db : self::$db; + return self::serverDSN().'/'.$db; + } + + static function &connect($dsn=null) { + static $instances; + if (!isset($instances)) $instances = array(); + + $dsn = $dsn ? $dsn : self::dsn(); + + // SET instances key + $key = md5($dsn); + + if (!isset($instances[$key])) { + $db = MDB2::factory($dsn, array('portability'=>MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL, 'debug'=>0)); + if (MDB2::isError($db)) { + $db->addUserInfo('line ' . __LINE__ . ' with dsn=' . $dsn); + return $db; + } + $db->setFetchMode(MDB2_FETCHMODE_ASSOC); + $instances[$key] =& $db; + } + + return $instances[$key]; + } + + function &disconnect(&$db) { + return $db->disconnect(); + } + + + /** + * seed loads and executes sql from a file + * + * @access public + * @param MDB2::connection &$connection + * @param string $filename + * @return string empty on success, error message on failure + */ + + static function seed(&$connection, $filename){ + if (! file_exists($filename)) { + return("NDB::seed() error: Cannot find file '$filename' \n"); + } + + $raw_contents = file_get_contents($filename); + + // Remove comments + $comment_patterns = array('/\/\*.*(\n)*.*(\*\/)?/', //C comments + '/\s*--.*\n/', //inline comments start with -- + '/\s*#.*\n/', //inline comments start with # + ); + $contents = preg_replace($comment_patterns, "\n", $raw_contents); + + // Retrieve sql statements + $statements = explode(";\n", $contents); + $statements = preg_replace("/\s/", ' ', $statements); + + foreach ($statements as $query) { + if (trim($query) != '') { + $res = $connection->exec($query); + if (PEAR::isError($res)) { + die($res->getMessage()); + } + } + } + } +} +?> diff --git a/lib/n_debug.php b/lib/n_debug.php new file mode 100644 index 0000000..913f2f1 --- /dev/null +++ b/lib/n_debug.php @@ -0,0 +1,73 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NDebug extends Object { + + static function debug($message, $debug_type = N_DEBUGTYPE_INFO, $log_level = PEAR_LOG_DEBUG, $ident=false) { + $debug_level = defined('DEBUG_LEVEL')?constant('DEBUG_LEVEL'):0; + if (empty($debug_level) || (is_numeric($debug_level) && $debug_level < $log_level)) { + return; + } + $debug_type_setting = defined('DEBUG_TYPE')?constant('DEBUG_TYPE'):0; + if (empty($debug_type_setting) || !is_numeric($debug_type_setting) || !($debug_type_setting & $debug_type)) { + return; + } + // make message intelligible + if (!is_string($message)) { + $message = print_r($message,true); + } + $filename = NDebug::getFilename($debug_type); + if ($ident == false) $ident = ucwords(APP_NAME); + $log = Log::singleton('file', NDebug::getDir() . $filename, $ident); + $log->log($message, $log_level); + unset($log); + } + + static function getFilename($debug_type) { + $filename = date('Y-m-d'); + if ($debug_type == N_DEBUGTYPE_SQL) { + $filename = 'sql_' . $filename; + } + return $filename; + } + + static function getDir() { + $dir = defined('CACHE_DIR')?CACHE_DIR . '/logs/':'/tmp/'; + return $dir; + } +} +?> diff --git a/lib/n_dispatcher.php b/lib/n_dispatcher.php new file mode 100644 index 0000000..c3ef767 --- /dev/null +++ b/lib/n_dispatcher.php @@ -0,0 +1,236 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NDispatcher extends Object { + var $url = ''; + var $params = array(); + var $app_dir = false; + var $controller = null; + var $action = null; + var $parameter = null; + + function __construct($url = '') { + if (defined('GZIP_COMPRESSION') && GZIP_COMPRESSION) ob_start("ob_gzhandler"); + $this->_parseURL($url); + $this->setParams($url); + parent::__construct($url); + } + + /** + * Set controller, action, [parameter, app_dir] for future calls to dispatch(). + * @see dispatch + * @access public + * @return null + */ + function setParams(){ + // since we got this far, we need to rewrite $_SERVER['PHP_SELF'] to a pared down $_SERVER['REQUEST_URI'] + $query_string = array_key_exists('QUERY_STRING', $_SERVER) ? $_SERVER['QUERY_STRING'] : ''; + $_SERVER['PHP_SELF'] = str_replace('?' . $query_string, '', $_SERVER['REQUEST_URI']); + NDebug::debug('' . $_SERVER['REMOTE_ADDR'] . ' requested ' . $_SERVER['PHP_SELF'] , N_DEBUGTYPE_PAGE); + + // normalize the url + $url = $this->url; + $url = preg_replace("|index\..*$|i", "", $url); + $url = preg_replace("|\." . DEFAULT_PAGE_EXTENSION . "$|i", "", $url); + $url = preg_replace("|/$|", "", $url); + $url = preg_replace('|^/|', '', $url); + // explode into an array + $parts = explode('/', $url); + // check if it's an nterchange specific controller + if (isset($parts[0]) && $parts[0] == APP_DIR) { + $this->app_dir = true; + $app = array_shift($parts); + if (empty($parts)) { + $loc = '/' . APP_DIR . '/dashboard'; + $qs = ''; + foreach ($_GET as $k=>$v) { + $qs .= ($qs?'&':'?') . $k . '=' . urlencode($v); + } + $loc .= $qs; + header('Location:' . $loc); + exit; + } + } + // set the controller, method and parameter and invoke + $this->controller = isset($parts[0])?$parts[0]:null; + $this->action = isset($parts[1])?$parts[1]:null; + $this->parameter = isset($parts[2])?$parts[2]:null; + } + + /** + * Instantiates and invokes the controller if it's available. + * @see _invoke + * @access public + * @return null + */ + function dispatch() { + $this->_invoke($this->controller, $this->action, $this->parameter); + } + + /** + * Instantiates and invokes the controller if it's available. + * + * @access private + * @param string $controller + * @param string $action the action to be performed + * @param string $parameter + * @return null + * + */ + function _invoke($controller, $action, $parameter=null) { + if (!$this->app_dir) { + $controller = 'page'; + } + if (!NController::exists($controller)) { + $this->error($controller, $action); + } + $ctrl = &NController::factory($controller); + if (!$action && method_exists($ctrl, 'index')) { + $action = 'index'; + } + if (!$this->app_dir && !in_array($action, $ctrl->public_actions)) { + $action = 'index'; + } + $method = Inflector::camelize($action); + $ctrl->action = $action; + if ($ctrl->login_required === true || (is_array($ctrl->login_required) && (in_array($action, $ctrl->login_required) || in_array($method, $ctrl->login_required)))) { + include_once 'n_auth.php'; + $ctrl->_auth = new NAuth(); + } + if (!$ctrl->checkUserLevel()) { + header('Location:/' . APP_DIR . '/'); + exit; + } + + // do the method + if (!$this->app_dir && $controller == 'page') { + $model = &$ctrl->getDefaultModel(); + // /_page8 redirection support (BC fix) + if (preg_match('|^/_page(\d+)|', $this->url, $matches)) { + $parameter = $matches[1]; + if ($page_info = $model->getInfo($parameter)) { + header('Location:' . $ctrl->getHref($page_info) . ($this->params?'?' . $this->paramsToString():'')); + exit; + } + } + if ($action != 'menus') { + $parameter = $ctrl->models['page']->URLToID($this->url); + } + } + if (method_exists($ctrl, $method)) { + $ctrl->$method($parameter); + if ($ctrl->auto_render) { + $ctrl->render(); + } + } else { + $this->error($ctrl, $method); + } + unset($ctrl); + } + + function paramsToString() { + $str = ''; + foreach ($this->params as $k=>$v) { + $str .= ($str?'&':'') . "$k=$v"; + } + return $str; + } + + /** + * + * @param string $url + * @access private + * @return null + */ + function _parseURL($url='') { + if ($url == '/' || preg_replace('/\?.*/', '', $url) == '/index.php') { + $url = isset($_GET['url'])?$_GET['url']:$url; + if (count($_GET > 1)) { + $get = ''; + foreach ($_GET as $key=>$val) { + if ($key != 'url') + $get .= ($get!=''?'&':'') . "$key=$val"; + } + $url .= "?$get"; + } + } else { + $url = $this->_normalizeURL($url); + } + require_once 'Net/URL.php'; + $ourl = new Net_URL($url); + $this->url = $ourl->path; + $this->params = $ourl->querystring; + if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') { + foreach ($_POST as $key=>$val) { + $this->params[$key] = $val; + } + } + unset($ourl); + } + + function _normalizeURL($url) { + include_once 'n_server.php'; + preg_match('|([^\?$]+)[\?$]?([^$]*)|', $url, $matches); + if (!empty($matches[2])) { + $get = ''; + foreach ($_GET as $key=>$val) { + $get .= ($get?'&':'') . $key . '=' . (is_string($val)?urlencode($val):$val); + } + $url = $matches[1] . ($get?'?' . $get:''); + } + return $url; + } + + function error(&$controller, $action=null) { + if (is_string($controller)) { + $values = array('_NOTICE_'=>'

      ERROR

      The ' . (is_string($controller)?$controller:$controller->name) . ' controller does not exist.'); + } else { + $values = array('_NOTICE_'=>'

      ERROR

      The ' . $controller->name . ' controller does not contain the ' . $action . ' action.'); + } + $view = &NView::singleton($controller); + $view->assign($values); + $view->render(array('layout'=>'plain')); + exit; + } + + /** + * Prints out the error 404 page + * + * @access public + * @param string $url the url of the page that caused the 404 error + * @return null + */ + function error404() { + print << + +404 Not Found + +

      Not Found

      +

      The requested URL {$_SERVER['REQUEST_URI']} was not found on this server.

      +
      +{$_SERVER['SERVER_SIGNATURE']} + +EOF; + } +} +?> diff --git a/lib/n_download.php b/lib/n_download.php new file mode 100644 index 0000000..f17faed --- /dev/null +++ b/lib/n_download.php @@ -0,0 +1,138 @@ + + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Persistent Download URLs for uploaded files. + * @author Darron Froese + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.13 + */ +class NDownload extends Object { + var $model; + var $field; + var $asset_id; + + /** + * cleanUrl - Clean up the passed url by removing UPLOAD_DIR and the leading / + * + * @param string The initial full URL. + * @return string + **/ + function cleanUrl($url) { + // Clean up the URL a bit. + $url = str_replace(UPLOAD_DIR, '', $url); + $url = eregi_replace('^/', '', $url); + return $url; + } + + /** + * getAssetAttributes - Split the URL by /'s and return an array. + * + * @param string The cleaned url. + * @return array + **/ + function getAssetAttributes($url) { + $url_parts = explode('/', $url); + return $url_parts; + } + + /** + * setAssetAttributes - Setting some class attributes: model, field and asset_id + * + * @param array An array from getAssetAttributes + **/ + function setAssetAttributes($url_parts) { + $this->model = $url_parts[0]; + $this->field = $url_parts[1]; + $this->asset_id = $url_parts[2]; + } + + /** + * getAssetModelName - Returns the asset model name. + * + * @return string The name of the model set in setAssetAttributes + **/ + function getAssetModelName() { + return $this->model; + } + + /** + * getFilePath - Returns the full path to the filename + * referenced in model/field/asset_id + * + * @return string The filename from the db plus DOCUMENT_ROOT. + **/ + function getFilePath() { + if (is_numeric($this->asset_id)) { + $object = NModel::factory($this->model); + $object->id = $this->asset_id; + if ($object->find()) { + while ($object->fetch()) { + $filename = $object->{$this->field}; + } + return DOCUMENT_ROOT . $filename; + } + } else { + return false; + } + } + + /** + * getFileName - Strips off of the path and just returns the filename. + * + * @param string A fully qualified filename. + * @return string Returns the filename only - not the path. + **/ + function getFileName($path) { + $filename = basename($path); + return $filename; + } + + /** + * serveFile - Actually serves the file to the browser. + * + * @param string A fully qualified filename that needs to be sent to a browser. + **/ + function serveFile($file_path) { + // Find out the mime type of that file. + $mime_type = NFilesystem::getMimeType($file_path); + // Need the filename for later on. + $filename = $this->getFileName($file_path); + // Serve the file out like normal. + $fp = fopen($file_path, 'rb'); + // Send correct headers. + header("Content-Type: $mime_type"); + header("Content-Length: " . filesize($file_path)); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + fpassthru($fp); + } + +} + +?> diff --git a/lib/n_filesystem.php b/lib/n_filesystem.php new file mode 100644 index 0000000..4351f9d --- /dev/null +++ b/lib/n_filesystem.php @@ -0,0 +1,233 @@ + + * @author Darron Froese + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NFilesystem { + /** + * cleanFileName - Clean up and standardize any filename passed to it. + * + * @param string A raw filename ready to be cleaned. + * @return string A cleaned up filename. + **/ + function cleanFileName($str) { + $badchars = array('`','~','!','@','#','$','%','^','&','*','(',')','=','+',';',':','\'','\\','"',',','<','>','/','?'); + // spaces-to-dashes, multiple-space-to-single, remove badchars + return str_replace(' ', '-', trim(preg_replace('/ +/', ' ', strtolower(str_replace($badchars, '', $str))))); + } + + /** + * buildDirs - Recursively build a directory structure. + * + * @param string Path that needs to be created. + * @param string Folder permissions in octal format. + * @return boolean + **/ + function buildDirs($dir, $mode = 0755) { + if (is_dir($dir) || @mkdir($dir, $mode)) return true; + if (!NFilesystem::buildDirs(dirname($dir), $mode)) return false; + return @mkdir($dir, $mode); + } + + /** + * deleteFile - Delete a file - locally and remotely if required. + * + * @param string A filename - NOT fully qualified. + * @return boolean + **/ + function deleteFile($filename) { + $full_path = DOCUMENT_ROOT . $filename; + if (file_exists($full_path)) { + if (unlink($full_path)) { + NDebug::debug("$full_path was deleted." , N_DEBUGTYPE_INFO); + // Delete the file from the mirror server. + if (defined('MIRROR_SITE') && MIRROR_SITE) { + require_once 'n_mirror.php'; + $mirror = NMirror::getInstance(); + $mirror->connect(); + $mirror->deleteFile($filename); + $mirror->disconnect(); + unset($mirror); + } + return true; + } else { + NDebug::debug("$full_path was NOT deleted." , N_DEBUGTYPE_INFO); + return false; + } + } else { + NDebug::debug("$full_path was not found or already deleted." , N_DEBUGTYPE_INFO); + return false; + } + } + + /** + * deleteFolder - Deletes a folder + * + * @param string The full path to the folder. + **/ + function deleteFolder($folder) { + if (rmdir($folder)) { + NDebug::debug("Deleted $folder." , N_DEBUGTYPE_INFO); + } else { + NDebug::debug("Could NOT delete $folder." , N_DEBUGTYPE_INFO); + } + } + + /** + * deletePathRecursive - Deletes a file/folder and all it's contents + * + * @param string The file or folder to delete + * @return boolean True if successfully deleted + */ + function deletePathRecursive($path){ + $path = str_replace(DOCUMENT_ROOT, '', $path); + $path = DOCUMENT_ROOT.$path; + if (is_dir($path)){ + if (substr($path, -1) != '/') $path = $path.'/'; + $files = glob($path . '*', GLOB_MARK); + $empty = true; + foreach ($files as $file) { + if (!self::deletePathRecursive($file)) { $empty = false; } + } + if ($empty) { return rmdir($path); } + else { return false; } + } + if (is_file($path)) { return unlink($path); } + // Not a file or a directory? + NDebug::debug("Unable to remove: ".$path , N_DEBUGTYPE_INFO); + return false; + } + + /** + * download_icon - Called from a Smarty template to return a string with an icon + * image link. + * + * @param array A path to a filename. + * @return string An img tag with a particular image for the filetype passed. + **/ + function download_icon($params) { + $extension = NFilesystem::getExtension($params['file']); + $icon = '/images/icons/' . $extension . '.png'; + $icon_path = DOCUMENT_ROOT . $icon; + if (file_exists($icon_path)) { + return AssetTagHelper::imageTag($icon, 'Download'); + } else { + return AssetTagHelper::imageTag('/images/icons/default.png', 'Download'); + } + } + + /** + * getExtension - returns the extension for a particular fully qualified filename. + * + * @param string A fully qualified filename. + * @return string The file extension. + **/ + function getExtension($filename) { + preg_match("/\.[\w]{1,6}$/i", $filename, $matches); + $extension = array_pop($matches); + $extension = str_replace('.', '', $extension); + return $extension; + } + + /** + * getMimeType - Get the MIME type for a fully qualified filename. + * Uses the PECL fileinfo extension if available. + * + * @param string A fully qualified filename. + * @return string The MIME type for that file. + **/ + function getMimeType($filename) { + // Use the fileinfo pecl extension if it's available. + if (function_exists('finfo_open')) { + $handle = finfo_open(FILEINFO_MIME); + $mime_type = finfo_file($handle, $filename); + return $mime_type; + } else { + $extension = NFilesystem::getExtension($filename); + switch($extension) { + case "jpg": + return "image/jpeg"; + case "jpeg": + return "image/jpeg"; + case "gif": + return "image/gif"; + case "png": + return "image/png"; + case "pdf": + return "application/pdf"; + case "txt": + return "text/plain"; + case "doc": + return "application/msword"; + case "xls": + return "application/vnd.ms-excel"; + case "ppt": + return "application/vnd.ms-powerpoint"; + case "css": + return "text/css"; + case "js": + return "application/x-javascript"; + case "html": + return "text/html"; + case "xhtml": + return "application/xhtml+xml"; + case "zip": + return "application/zip"; + default: + return "application/octet-stream"; + } + } + } + + /** + * filesize_format - Called from a Smarty template to return a human readable + * representation of the size of the file. + * + * @param int Size of the file in bytes. + * @return string Size of the file in human readable format. + **/ + function filesize_format($bytes) { + if (0 >= $bytes) { + return $bytes; + } + $names = array('B','KB','MB','GB','TB','PB','EB','ZB','YB'); + $values = array(1, 1024, pow(1024,2), pow(1024,3), pow(1024,4), pow(1024,5), pow(1024,6), pow(1024,7), pow(1024,8)); + $i = floor(log($bytes)/6.9314718055994530941723212145818); //log(1024) = 6.9314718055994530941723212145818 + return number_format($bytes/$values[$i]) . ' ' . $names[$i]; + } + + /** + * download_time - Called from a Smarty template to return the number of seconds + * the file will take to download. + * + * @param int Size of the file in bytes. + * @param int Speed in K to estimate download time. + * @return int Seconds (estimated) it will take to download the file. + **/ + function download_time($bytes, $speed_in_k = 56) { + if (0 >= $bytes) { + return $bytes; + } + $k = round($bytes/1024, 0); + $seconds = round($k/($speed_in_k/10)); + return $seconds; + } +} +?> diff --git a/lib/n_flash.php b/lib/n_flash.php new file mode 100644 index 0000000..c000f45 --- /dev/null +++ b/lib/n_flash.php @@ -0,0 +1,113 @@ +flash->set('key', 'value'); + * $controller->flash->get('key'); + * $controller->now('key'); + * $controller->keep('key'); + * + * NOTE: You cannot use NFlash outside of nterchange at this moment. + * It's limited to nterchange in the contructor. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category NFlash + * @author Tim Glen + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NFlash extends Object { + var $keeps = array(); + var $nows = array(); + var $flashes = array(); + var $flashcounts = array(); + + static function &singleton() { + static $flash; + if (!isset($flash)) $flash = new NFlash; + return $flash; + } + + function set($key, $val) { + $this->flashes[$key] = $val; + $this->flashcounts[$key] = 0; + } + + function get($key) { + return isset($this->flashes[$key])?$this->flashes[$key]:false; + } + + function now($key) { + $this->nows[] = $key; + } + + function keep($key) { + $this->keeps[] = $key; + } + + function exists($key) { + return isset($this->flashes[$key])?true:false; + } + + function __construct() { + if ((defined('IN_NTERCHANGE') && IN_NTERCHANGE) || (defined('GLOBAL_SESSION_COOKIE') && GLOBAL_SESSION_COOKIE == true)) { + @session_start(); + } elseif (defined('GLOBAL_SESSION_COOKIE') && GLOBAL_SESSION_COOKIE == false) { + // Do nothing. + } else { + // Default behaviour + @session_start(); + } + + // load any flashes from the last page + if ((!isset($_SESSION['_flashinit']) || !$_SESSION['_flashinit']) && isset($_SESSION['_flash'])) { + foreach ($_SESSION['_flash'] as $key=>$val) { + $_SESSION['_flashcount'][$key]++; + $this->flashes[$key] = $val; + $this->flashcounts[$key] = $_SESSION['_flashcount'][$key]; + } + } + + $_SESSION['_flashinit'] = 1; + + parent::__construct(); + register_shutdown_function(array(&$this, '__destruct')); + } + function __destruct() { + // kill any un"kept" flashes + if (isset($_SESSION['_flash'])) { + foreach ($_SESSION['_flash'] as $key=>$val) { + if ($_SESSION['_flashcount'][$key] > 0 && !in_array($key, $this->keeps)) { + unset($_SESSION['_flash'][$key]); + unset($_SESSION['_flashcount'][$key]); + unset($this->flashes[$key]); + unset($this->flashcounts[$key]); + } else if (in_array($key, $this->keeps)) { + $this->flashcounts[$key] = 0; + $_SESSION['_flashcount'][$key] = 0; + } + } + } + // load any un"nowed" flashes + foreach ($this->flashes as $key=>$val) { + if (!in_array($key, $this->nows)) { + $_SESSION['_flash'][$key] = $val; + $_SESSION['_flashcount'][$key] = 0; + } + } + $_SESSION['_flashinit'] = 0; + } +} +?> diff --git a/lib/n_image.php b/lib/n_image.php new file mode 100644 index 0000000..fd298b5 --- /dev/null +++ b/lib/n_image.php @@ -0,0 +1,212 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.16 + */ +class NImage extends Object { + // List of image formats we can convert to jpeg. These are all tested and work. + // NOTE: PDF can do some odd things (sizes and cropping) - but it does convert. + var $bad_formats = array ('PSD', 'TIFF', 'BMP', 'PS', 'PCD', 'WMF', 'PDF'); + var $convert_binary = '/usr/bin/convert'; + var $identify_binary = '/usr/bin/identify'; + + function __construct() { + if (class_exists('Imagick')) { + // Yay. + } else { + die('You need Image Magick to continue'); + } + } + + /** + * imageResize - Resize an image proportionally in place with Image Magick's thumbnailImage method. + * Resaves with the same filename and overwrites the original file. + * + * @param string The name of the file - NOT fully qualified. + * @param int The width of the final image. + * @param int The height of the final image. + * @return void + **/ + function imageResize($file, $width=0, $height=0) { + $filename = $_SERVER['DOCUMENT_ROOT'] . $file; + $image = new Imagick(); + $image->readImage("$filename"); + $image->thumbnailImage($width,$height); + $image->writeImage("$filename"); + $image->destroy(); + } + + /** + * checkImageFormat - Converts CMYK images to RGB format and converts $this->bad_formats + * to $converted_format if required. + * + * @param string The format to convert to. + * @param string The file to be checked - NOT fully qualified. + * @param string The name of the asset. + * @param string The field in the database where this file is referenced. + * @param int The id of this particular asset record. + * @todo Should we notify the user if they've uploaded a file in the bad format? + * @todo We need to make this type of thing happen for multiple files and only save it to the DB once. + * @return void + **/ + function checkImageFormat($converted_format, $file, $asset=null, $field=null, $id=null) { + $format = $this->getImageFormat($file); + // Check to see if it's a CMYK image. + if ($cmyk = $this->isImageCMYK($file)) { + $this->convertCMYKToRGB($file); + } + // Check to see if it's a bad format - if it is, convert to $converted_format. + // and save the changed filename to the database. + if ($bad_format = $this->isImageBadFormat($format)) { + $filename = $this->setImageFormat($file, $converted_format); + $filename = str_replace($_SERVER['DOCUMENT_ROOT'], '', $filename); + // Gotta save it to the DB now since the name has changed. + $this->saveUpdatedImage($filename, $asset, $field, $id); + } + } + + /** + * saveUpdatedImage - Saves an image's new filename back to the database. + * + * @param string The file to be checked - NOT fully qualified. + * @param string The name of the asset. + * @param string The field in the database where this file is referenced. + * @param int The id of this particular asset record. + * @return void + **/ + function saveUpdatedImage($filename, $asset, $field, $id) { + $upload_model = NModel::factory($asset); + $upload_model->id = $id; + if ($upload_model->find()) { + while ($upload_model->fetch()) { + $upload_model->{$field} = $filename; + $upload_model->save(); + } + } + } + + /** + * getImageFormat - Returns the Image Format from a file via Imagick. + * + * @param string The file we want the format for - NOT fully qualified. + * @return string The format of the file. + **/ + function getImageFormat($file) { + $filename = $_SERVER['DOCUMENT_ROOT'] . $file; + $image = new Imagick(); + $image->readImage("$filename"); + $format = $image->getImageFormat(); + $image->destroy(); + return $format; + } + + /** + * isImageBadFormat - Is the format of this image in $this->bad_formats? + * + * @param string A format from a particular file. + * @return boolean + **/ + function isImageBadFormat($format) { + if (in_array($format, $this->bad_formats)) { + return true; + } else { + return false; + } + } + + /** + * compressJPEGImage - Compress a JPEG image - Level is from 0-100. + * Resaves with the same filename and overwrites the original file. + * + * @param string A JPEG image to be compressed - NOT fully qualified. + * @param int The level to be compressed. 0 is least compressed - 100 is most. + * @return void + **/ + function compressJPEGImage($file, $level) { + $filename = $_SERVER['DOCUMENT_ROOT'] . $file; + $image = new Imagick(); + $image->readImage("$filename"); + $image->setCompressionQuality($level); + $image->writeImage("$filename"); + $image->destroy(); + } + + /** + * setImageFormat - Sets an image to a particular image format. + * Resaves with a NEW filename to reflect the new format. + * + * @param string The filename - NOT fully qualified. + * @param string The new format to convert the image to. + * @return string The new filename. + **/ + function setImageFormat($file, $format) { + $filename = $_SERVER['DOCUMENT_ROOT'] . $file; + $image = new Imagick(); + $image->readImage("$filename"); + $image->setImageFormat($format); + $new_filename = $filename . "." . strtolower($format); + $image->setFilename($new_filename); + $image->writeImage("$new_filename"); + $image->destroy(); + return $new_filename; + } + + /** + * isImageCMYK - Determines whether or not an image is in CMYK format. + * + * @param string The filename - NOT fully qualified. + * @return boolean + * @todo There's gotta be a better way to do this - but I can't get getImageColorspace to return anything. + **/ + function isImageCMYK($filename) { + $filename = $_SERVER['DOCUMENT_ROOT'] . $filename; + $identify_command = $this->identify_binary . ' -format "%r" ' . $filename; + exec($identify_command, $identify_output_array); + $identify_output = implode(' ', $identify_output_array); + if (eregi('CMYK', $identify_output)) { + return true; + } else { + return false; + } + } + + /** + * convertCMYKToRGB - Convert a CMYK image to RGB. + * Resaves with the same filename and overwrites the original file. + * + * @param string The filename - NOT fully qualified. + * @return void + * @todo There's gotta be a better way to do this - but the normal way doesn't appear to work. + * @todo Apparently "profiles" are the proper way to do this that produces the best result. Refactor when they actually work. + **/ + function convertCMYKToRGB($filename) { + $filename = $_SERVER['DOCUMENT_ROOT'] . $filename; + $directory = dirname($filename); + $tmp_file = $directory . '/convert.jpg'; + $convert_command = $this->convert_binary . ' -colorspace RGB ' . $filename . " $tmp_file"; + exec($convert_command, $convert_output_array); + copy($tmp_file, $filename); + unlink($tmp_file); + } +} + +?> \ No newline at end of file diff --git a/lib/n_mirror.php b/lib/n_mirror.php new file mode 100644 index 0000000..cd2ccab --- /dev/null +++ b/lib/n_mirror.php @@ -0,0 +1,96 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.16 + */ +class NMirror extends Object { + + function __construct() { + + } + + /** + * getInstance - Returns an instance object of a Mirror child class. + * Depends on what is set for MIRROR_TYPE in the conf/conf.php + * + * @return object + **/ + function getInstance() { + if (defined('MIRROR_TYPE') && MIRROR_TYPE == 'ftp') { + return new FtpMirror(); + } elseif (defined('MIRROR_TYPE') && MIRROR_TYPE == 's3') { + return new S3Mirror(); + } elseif (defined('MIRROR_TYPE') && MIRROR_TYPE == 'rsync') { + return new RsyncMirror(); + } else { + return false; + } + } + + /** + * connect - connect to the remote server + * + * @return void + **/ + function connect() { + + } + + /** + * disconnect - disconnect from the remote server + * + * @return void + **/ + function disconnect() { + + } + + /** + * putFile - Put a file on the remote server. + * + * @return void + **/ + function putFile() { + + } + + /** + * deleteFile - Delete a file from the remote server. + * + * @return void + **/ + function deleteFile() { + + } + + /** + * synchronizeDirectory - Synchronize an entire directory from local to remote. + * + * @return void + **/ + function synchronizeDirectory() { + + } + +} + +?> \ No newline at end of file diff --git a/lib/n_model.php b/lib/n_model.php new file mode 100644 index 0000000..7b6f69b --- /dev/null +++ b/lib/n_model.php @@ -0,0 +1,1230 @@ +_call($method,$args,$return); + return $return; + } + function __sleep() { + return array_keys(get_object_vars($this)) ; + } + function __construct() { + parent::__construct(); + } + function __destruct() { + parent::__destruct(); + } + } +} else { + if (!function_exists('clone')) { + // emulate clone - as per php_compact, slow but really the correct behaviour.. + eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }'); + } + eval(' + class NModel_Overload extends Object { + function __call($method,$args,&$return) { + return $this->_call($method,$args,$return); + } + function __construct() { + parent::__construct(); + } + function __destruct() { + parent::__destruct(); + } + } + '); +} + +/** + * NModel - Object Based Database Query Builder and data store + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.01 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_01.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Model + * @author Tim Glen + * @author Alan Knowles + * @copyright 1997-2007 nonfiction studios inc. (not sure if this is correct) + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NModel extends Object implements ArrayAccess { + /** + * Container for the PEAR::DB object + * + * @var object + * @access private + */ + var $_db = null; + /** + * Name of the table the model is associated with. + * + * This value is set in the __construct of the classes extending NModel + * + * @var string + * @access private + */ + var $__table = null; + /** + * Holds the table config + * + * Holds a hash of field=>definition values for each table field. + * Definitions are computed values of the bit-value types: + * N_DAO_INT, N_DAO_STR, N_DAO_DATE, N_DAO_TIME, + * N_DAO_BOOL, N_DAO_TXT, N_DAO_BLOB, N_DAO_FLOAT, + * N_DAO_NOTNULL, N_DAO_MYSQLTIMESTAMP + * + * @see N_Model::table() + * @var array + * @access private + */ + var $_config = array(); + /** + * The fields that acts as the primary_key + * + * @see NModel::primaryKey() + * @var string + * @access public + */ + var $primary_key = 'id'; + /** + * Simple array with a list of the tables fields. + * + * @see NModel::fields() + * @var string + * @access private + */ + var $_fields = array(); + /** + * Holds the most recent DB_Result object + * + * @see NModel::_query() + * @var object + * @access private + */ + var $_result = null; + /** + * Holds the number of rows selected/affected by the last query + * + * @see NModel::_query() + * @var int + * @access private + */ + var $_numrows = null; + /** + * Tracks the current row in a fetch loop + * + * @see NModel::fetch() + * @var int + * @access private + */ + var $_current_row = 0; + /** + * Holds the last executed sql query string + * + * @see NModel::fetch() + * @var string + * @access private + */ + var $_last_query = null; + /** + * Holds a virgin copy of the the _query array + * + * @see NModel::_query + * @var array + * @access private + */ + var $_query_orig = null; + /** + * A query array used to build final queries + * + * @see NModel::find() + * @var array + * @access private + */ + var $_query = null; + /** + * The SELECT portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_select = '*'; + /** + * The WHERE portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_conditions = ''; + /** + * The GROUP BY portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_group_by = ''; + /** + * The ORDER BY portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_order_by = ''; + /** + * The HAVING portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_having = ''; + /** + * The LIMIT portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_limit = ''; + /** + * The OFFSET for the limit portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_offset = '0'; + /** + * The JOIN (inner, outer, left, etc) portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_join = ''; + /** + * The INCLUDE portion of the string to be part of the default query + * + * @see NModel::_buildQuery() + * @var string + * @access private + */ + var $_include = ''; + + var $table_name_prefix = ''; + var $table_name_suffix = ''; + + // form settings + + /** + * Field to read for the Form Header in CRUD operations + * + * Refers to a table field + * + * @todo This needs to get moved to the NModel class + * @var string + * @access public + */ + var $form_header = '_headline'; + + /** + * Field to use for record display + * + * Can either be a string, referring to a table field or + * an array, referring to multiple fields + * + * @todo This needs to get moved to the NModel class + * @var mixed + * @access public + */ + var $_headline = null; + /** + * Predefine fields that the auto-interpreter will get wrong + * + * The interpreter doesn't really get textareas, selects or a couple + * others. If you want to customize a field for the form, add it here. + * + * Usage: + * $this->form_elements['field_name'] = array('select', 'field_name', 'Field Label', array('select'=>'select', 'options'=>'options')); + * + * @var array + * @access public + */ + var $form_elements = array(); + /** + * Show only these fields + * + * @var array + * @access public + */ + var $form_display_fields = array(); + /** + * Do not show these fields + * + * @var array + * @access public + */ + var $form_ignore_fields = array(); + /** + * Default values for specific fields + * + * Usage: + * $this->form_field_defaults['field_name'] = 'default_value'; + * + * @var array + * @access public + */ + var $form_field_defaults = array(); + /** + * Label names for specific fields + * + * Labels are auto-created, but if you want them customized, + * simply change them here + * + * Usage: + * $this->form_field_labels['field_name'] = 'Form Field Label'; + * + * @var array + * @access public + */ + var $form_field_labels = array(); + /** + * Special options for the field element + * + * Usage: + * $this->form_field_options['field_name'] = array('option'=>'value'); + * + * @var array + * @access public + */ + var $form_field_options = array(); + /** + * Special html attributes for the field element + * + * Usage: + * $this->form_field_attributes['field_name'] = array('class'=>'fieldclass'); + * + * @var array + * @access public + */ + var $form_field_attributes = array(); + /** + * Validation check - required fields + * + * @var array + * @access public + */ + var $form_required_fields = array(); + /** + * Validation check - rules + * + * @var array + * @access public + */ + var $form_rules = array(); + /** + * Fields that will require bitmask functionality + * + * @var array + * @access public + */ + var $bitmask_fields = array(); + /** + * Delete the associated file uploads + */ + var $_delete_uploads = true; + + function __construct() { + $db = &NDB::connect(); + if (PEAR::isError($db)) { + die('Can\'t connect to the db.'); + } else { + $this->_db = &$db; + } + $this->_loadConfig(); + foreach ($this->_config as $field=>$def) { + $this->_fields[] = $field; + $this->$field = null; + } + $this->table_name_prefix = defined('DB_TABLE_NAME_PREFIX')?constant('DB_TABLE_NAME_PREFIX'):''; + $this->table_name_suffix = defined('DB_TABLE_NAME_SUFFIX')?constant('DB_TABLE_NAME_SUFFIX'):''; + + // load $this->_query with defaults + // the defaults can be overridden in child classes + $this->_buildQuery(); + // save the original query before we can do any damage + $this->_query_orig = $this->_query; + parent::__construct(); + } + + static function &factory($model) { + $model_class = Inflector::camelize($model); + $path = '%s/app/models/%s.php'; + if (file_exists(sprintf($path, ROOT_DIR, $model))) { + include_once sprintf($path, ROOT_DIR, $model); + } else if (file_exists(sprintf($path, BASE_DIR, $model))) { + include_once sprintf($path, BASE_DIR, $model); + } + if (class_exists($model_class)) { + $ret = new $model_class; + } else { + // TODO: raise an error here + $ret = false; + } + return $ret; + } + + /** + * Singleton pattern to return the same model from wherever it is called + * + * @param string $model - should be an underscored word + * @see NModel::factory + * @return object + */ + function &singleton($model) { + static $models; + if (!isset($models)) $models = array(); + $key = md5($model); + if (!isset($models[$key])) { + $models[$key] = &NModel::factory($model); + } + return $models[$key]; + } + + function tableName($add_prefix_suffix = false) { + return $add_prefix_suffix?$this->table_name_prefix . $this->__table . $this->table_name_suffix:$this->__table; + } + + function setHeadline($var) { + $this->_headline = $var; + } + + function getHeadline() { + return $this->_headline; + } + + function makeHeadline($separator = '-') { + $str = ''; + $fields = $this->fields(); + $headline = $this->getHeadline()?$this->getHeadline():(in_array('cms_headline', $fields)?'cms_headline':null); + if (is_array($headline)) { + foreach ($headline as $field) { + // Deal with foreign keys so the the custom headline isn't just id numbers. + if (eregi('_id$', $field)) { + $model_name = eregi_replace('_id$', '', $field); + if ($model_instance = $this->getLink($field, $model_name)) { + $new_headline = $model_instance->cms_headline; + $str .= ($str?" $separator ":'') . $new_headline; + } + } else { + $str .= ($str?" $separator ":'') . $this->$field; + } + } + } else if ($headline) { + $str .= $this->$headline; + } + return $str; + } + + function get($v=null) { + if (!isset($this->_query)) { + // TODO: raise an error + return false; + } + // $v is the primary key + if (empty($v)) { + return false; + } + $pk = $this->primaryKey(); + $this->$pk = $v; + return $this->find(null, true); + } + + function find($options = array(), $autofetch = false) { + if (!isset($this->_query)) { + // TODO: raise an error + return false; + } + $db = &$this->_db; + $query = $this->_query; + $first_only = isset($options['first']) && $options['first']; + if ($first_only) { + $query['limit'] = 1; + $query['offset'] = 0; + if (isset($options['limit'])) unset($options['limit']); + if (isset($options['offset'])) unset($options['offset']); + } + if (is_array($options)) { + foreach ($query as $key=>$val) { + $query[$key] = isset($options[$key]) && $options[$key]?$options[$key]:$query[$key]; + } + } else if (is_int($options) || is_string($options)) { + // Should I be doing this? + $pk = $this->primaryKey(); + $this->$pk = (int) $options; + } + $query = $this->_buildConditions($query, $this->table()); + $sql = 'SELECT' . + ' ' . $query['select'] . + ' FROM ' . $this->tableName(true) . + ($query['join']?' ' . $query['join']:'') . + ($query['include']?' ' . $query['include']:'') . + ($query['conditions']?' WHERE ' . $query['conditions']:'') . + ($query['group_by']?' GROUP BY ' . $query['group_by']:'') . + ($query['having']?' HAVING ' . $query['having']:'') . + ($query['order_by']?' ORDER BY ' . $query['order_by']:''); + if (!empty($query['limit']) && strlen($query['limit'] . $query['offset'])) { + $db->setLimit($query['limit'], $query['offset']); + } + $this->debug('SQL: ' . $sql, N_DEBUGTYPE_SQL); + $this->_query($sql); + if (NModel::isError($this->_result)) { + // TODO: raise an error here? or return it? + return false; + } + if ($autofetch) { + $this->fetch(); + } + return $this->_numrows; + } + + function fetch() { + if (empty($this->_numrows)) { + return false; + } + if (NModel::isError($this->_result)) { + // an error should have been return by the find(). Even if it was ignored, just return false + return false; + } + $arr = $this->_result->fetchRow(MDB2_FETCHMODE_ASSOC); + if ($arr === null) { + // this is likely the end of the data + return false; + } + + $table = $this->table(); + foreach ($arr as $k=>$v) { + $this->$k = $v; + } + $this->_current_row++; + if (isset($this->_query)) { + unset($this->_query); + } + return true; + } + + function fetchAll($to_array = false) { + if (empty($this->_numrows)) { + return array(); + } + if (PEAR::isError($this->_result)) { + return false; + } + $ret = array(); + while ($this->fetch()) { + $ret[] = $to_array?$this->toArray():clone($this); + } + return $ret; + } + + function toArray() { + $fields = $this->fields(); + $ret = array(); + foreach ($fields as $field) { + $ret[$field] = $this->$field; + if (isset($this->{'_' . $field})) { + $ret['_' . $field] = $this->{'_' . $field}->toArray(); + } + } + return $ret; + } + + /** + * offsetExists - ArrayAccess method + * if $this->key is set, array_key_exists('key', $this) will be true + */ + public function offsetExists($offset) { + return method_exists($this, $offset) || property_exists($this, $offset); + } + + /** + * offsetGet - ArrayAccess method + * @param string $offset the array key, eg: $this['key'] + * @return mixed the value stored at $this->key + * @throws Exception If key doesn't exist + */ + public function offsetGet($offset) { + if (property_exists($this, $offset)) + return $this->$offset; + elseif (method_exists($this, $offset)) + return $this->$offset(); + else + $this->debug("Attempted access to undefined offset: $offset" , N_DEBUGTYPE_INFO); + } + + /** + * offsetSet - ArrayAccess method + * @param string $offset The name of the property + * @param mixed $value + */ + public function offsetSet($offset, $value) { + $this->$offset = $value; + } + + /** + * offsetUnset - ArrayAccess method + * @param string $offset The key to unset + */ + public function offsetUnset($offset) { + if (property_exists($this, $offset)) + unset($this->$offset); + } + + /** + * Magic Method for undefined property + * Provides support for Uniform Access Principle + * $this->method_name will call $this->method_name() + * if the property doesn't exist + */ + public function __get($name){ + if (method_exists($this, $name)) + return $this->$name(); + } + + + function save($dao = false, $convertTimeToUTC = true) { + $pk = $this->primaryKey(); + return isset($this->$pk) && isset($this->$pk)?$this->update($dao,$convertTimeToUTC):$this->insert($dao); + } + + /** + * insert creates the sql for a database insert and executes it + * + * @access public + * @param object $dao + * @return int + */ + function insert($dao=false) { + $pk = $this->primaryKey(); + + $db = &$this->_db; + $dsn = $db->getDSN('array'); + $dbtype = $dsn['phptype']; + $fields = $this->table(); + $fields_str = ''; + $val_str = ''; + $this->beforeCreate(); + foreach ($fields as $k=>$def) { + // if it's null or doesn't exist, then skip it + if (!isset($this->$k)) { + continue; + } + // don't write to the primary key + if ($k == $pk) { + continue; + } + // can't insert into a mysql timestamp + if ($def & N_DAO_MYSQLTIMESTAMP) { + continue; + } + // if it's started, put a comma + $fields_str .= $fields_str?', ':''; + $val_str .= $val_str?', ':''; + // auto-add $k since we know it will be used at this point + $fields_str .= $k; + // Since an insert does an update anyways - this isn't needed on insert. + //if ($def & N_DAO_TIME && !preg_match('/^cms_/', $k)) { + // $this->$k = $this->convertTimeToUTC($k, $this->$k); + //} + // handle null values (if 'null' string) + if ('null' === strtolower($this->$k) && !($def & N_DAO_NOTNULL)) { + $val_str .= 'NULL'; + continue; + } + // add the value as a string + if ($def & N_DAO_STR) { + $val_str .= $db->quote($this->$k, 'text'); + continue; + } + // add the value as an ints + if (is_numeric($this->$k)) { + $val_str .= $this->$k; + continue; + } + // add the value as a string by default + $val_str .= $db->quote($this->$k); + } + if ($fields_str) { + $sql = 'INSERT INTO ' . $this->tableName(true) . " ({$fields_str}) VALUES ({$val_str})"; + $this->debug('SQL: ' . $sql, N_DEBUGTYPE_SQL); + $res = &$this->exec($sql); + + if (PEAR::isError($res)) { + // TODO: raise error (insert error) + return false; + } + if ($res < 1) { + return 0; + } + + // get the id and set/return it + if ($pk) { + if ($this->$pk = $this->_db->lastInsertID($this->tableName(true), $pk)) { + $this->afterCreate($this->$pk); + return $this->$pk; + } + } + return true; + } + // TODO: raise error (no values passed) + return false; + } + + function update($dao = false, $convertTimeToUTC = true) { + $pk = $this->primaryKey(); + if (empty($this->$pk)) { + // TODO: raise error + return false; + } + + $db = &$this->_db; + $fields = $this->table(); + $set_str = ''; + $this->beforeUpdate(); + foreach ($fields as $k=>$def) { + // if it's null or doesn't exist, then skip it + if (!isset($this->$k)) { + continue; + } + // don't write to the primary key + if ($k == $pk) { + continue; + } + // can't insert into a mysql timestamp + if ($def & N_DAO_MYSQLTIMESTAMP) { + continue; + } + // if it's started, put a comma + $set_str .= $set_str?', ':' '; + // convert from user-entered time to the server's time zone + if ($def & N_DAO_TIME && !preg_match('/^cms_/', $k) && $convertTimeToUTC) { + $this->$k = $this->convertTimeToUTC($k, $this->$k); + } + // handle null values (if 'null' string) + if ('null' === strtolower($this->$k) && !($def & N_DAO_NOTNULL)) { + $set_str .= $k . '=NULL'; + continue; + } + // add the value as a string + if ($def & N_DAO_STR) { + $set_str .= $k . '=' . $db->quote($this->$k); + continue; + } + // add the value as an int + if (is_numeric($this->$k)) { + $set_str .= $k . '=' . $this->$k; + continue; + } + // add the value as a string by default + $set_str .= $k . '=' . $db->quote($this->$k); + } + $sql = 'UPDATE ' . $this->tableName(true) . ' SET' . $set_str . ' WHERE ' . $pk . '=' . $this->$pk; + $this->debug('SQL: ' . $sql, N_DEBUGTYPE_SQL); + $res = &$this->exec($sql); + if (PEAR::isError($res)) { + // TODO: raise error + return false; + } else { + $this->afterUpdate($res); + } + return $res; + } + + function delete() { + $pk = $this->primaryKey(); + $before_delete_pk = $this->$pk; + if (empty($this->$pk)) { + // TODO: raise error + return false; + } + $fields = $this->fields(); + if ($this->_delete_uploads) { $this->deleteUploadFolder(); } + if (in_array('cms_deleted', $fields)) { + $this->cms_deleted = 1; + $this->beforeDelete(); + $updated_id = $this->update(); + $this->afterDelete($before_delete_pk); + return $updated_id; + } + // otherwise, actually delete it + $this->beforeDelete(); + $sql = 'DELETE FROM ' . $this->tableName(true) . ' WHERE ' . $pk . '=' . $this->$pk; + $this->debug('SQL: ' . $sql, N_DEBUGTYPE_SQL); + $res = &$this->exec($sql); + if (PEAR::isError($res)) { + // TODO: raise error + return false; + } else { + $this->afterDelete($before_delete_pk, 1); + } + return $res; + } + + function deleteUploadFolder(){ + require_once 'lib/n_filesystem.php'; + $pk = $this->primaryKey(); + if (!$this->$pk) return; + $upload_dir = UPLOAD_DIR."/{$this->tableName(true)}/{$this->$pk}"; + NFilesystem::deletePathRecursive($upload_dir); + } + + function &getLink($field, $model) { + $model = &NModel::factory($model); + if (isset($this->$field) && $model && $model->get($this->$field)) { + return $model; + } + unset($model); + $ret = false; + return $ret; + } + + function reset() { + $table = $this->table(); + foreach ($table as $key=>$def) { + $this->$key = null; + } + $this->_query = $this->_query_orig; + $this->_current_row = 0; + return true; + } + + function quote($val) { + return $this->_db->quote($val); + } + + function query($sql) { + return $res = &$this->_db->query($sql); + } + + function exec($sql) { + return $res = &$this->_db->exec($sql); + } + + function loadQuery($sql) { + $this->reset(); + $this->_query($sql); + } + + function _query($sql) { + $db = &$this->_db; + + // let the db backend handle the transactions + // we'll just provide keyworkds + if (strtoupper($sql) == 'BEGIN') { + $db->autoCommit(false); + return true; + } + if (strtoupper($sql) == 'COMMIT') { + $db->commit(); + $db->autoCommit(true); + return true; + } + if (strtoupper($sql) == 'ROLLBACK') { + $db->rollback(); + $db->autoCommit(true); + return true; + } + // actually do the query + $res = &$this->query($sql); + $this->_last_query = $sql; + + if (MDB2::isError($res)) { + // TODO: what to do with the error? + return false; + } + switch (strtolower(substr(trim($sql),0,6))) { + case 'insert': + case 'update': + case 'delete': + return $db->affectedRows(); + } + if (is_object($res)) { + $this->_result = &$res; + } + $this->_numrows = 0; + if (method_exists($res, 'numrows')) { + $db->expectError(MDB2_ERROR_UNSUPPORTED); + $this->_numrows = $res->numrows(); + if (MDB2::isError($this->_numrows)) { + $this->_numrows = 1; + } + $db->popExpect(); + } + unset($this->_query); + } + + function _buildConditions($query, $keys, $filter=array()) { + foreach ($keys as $key=>$def) { + // if it's not in the filter, don't include it + if (!empty($filter) && !in_array($key, $filter)) { + continue; + } + // if the field is cms_deleted and it's null, then set to 0 (non-deleted); + if ($key == 'cms_deleted' && !isset($this->$key)) { + $this->$key = 0; + } + // if it's null, don't include it. + if (!isset($this->$key)) { + continue; + } + // it will definitely get added at this point, so check the condition + $query['conditions'] .= $query['conditions']?' AND':''; + // if it's "null" and it's a null field in the db + if ((strtolower($this->$key) === 'null') && !($def & N_DAO_NOTNULL)) { + $query['conditions'] .= ' ' . $this->tableName(true) . ".$key IS NULL"; + continue; + } + // if it's a string field in the db, then quote it + if ($def & N_DAO_STR) { + $query['conditions'] .= ' ' . $this->tableName(true) . ".$key" . '=' . $this->quote(($def & N_DAO_BOOL?(bool)$this->$key:$this->$key)); + continue; + } + // if it's a number, then just add it directly + if (is_numeric($this->$key)) { + $query['conditions'] .= ' ' . $this->tableName(true) . ".$key" . '=' . $this->$key; + continue; + } + // it shouldn't get to this point, but in case it does, cast it to an int... + $query['conditions'] .= ' ' . $this->tableName(true) . ".$key" . '=' . (int)$this->$key; + } + return $query; + } + + function _buildQuery() { + $this->_query = array(); + $this->_query['select'] = $this->_select; + $this->_query['conditions'] = $this->_conditions; + $this->_query['group_by'] = $this->_group_by; + $this->_query['order_by'] = $this->_order_by; + $this->_query['having'] = $this->_having; + $this->_query['limit'] = $this->_limit; + $this->_query['offset'] = $this->_offset; + $this->_query['join'] = $this->_join; + $this->_query['include'] = $this->_include; + + } + + // This is a default method to happen on the model data before every create. + function beforeCreate() { + // Call this from the model instance. + } + + // This is a default method to happen on the model data after every create. + function afterCreate($result_id=null) { + // Call this from the model instance. + } + + // This is a default method to happen on the model data before every update. + function beforeUpdate() { + // Call this from the model instance. + } + + // This is a default method to happen on the model data after every update. + function afterUpdate($result_id=null) { + // Call this from the model instance. + } + + // This is a default method to happen on the model data before every delete. + function beforeDelete() { + // Call this from the model instance. + } + + // This is a default method to happen on the model data after every delete. + function afterDelete($result_id=null, $destroyed=0) { + // Call this from the model instance. + } + + /** + * Run a pre-upload hook before uploading a file + * + * This method may be overridden to handle all file fields. To handle a + * specific field, for example 'large_image', define a method on your model + * like this: + * + * function beforeUpload_large_image($filepath) { + * // process file + * return $filepath; + * } + */ + function beforeUpload($field, $filepath) { + $hook = "beforeUpload_$field"; + if (method_exists($this, $hook)) { + $filepath = $this->$hook($filepath); + } + return $filepath; + } + + // TODO: id shouldn't be assumed - need to set up some key finding code. Also needs key descriptions in the _config + function primaryKey($key=null) { + if (is_string($key) && $key) { + $this->primary_key = $key; + } + return $this->primary_key; + } + + function table() { + $this->_loadConfig(); + return $this->_config; + } + + function fields() { + return $this->_fields; + } + + function lastQuery() { + return $this->_last_query; + } + + function currentRow() { + $this->_current_row; + } + + function numRows() { + return $this->_numrows; + } + + function now() { + include_once 'n_date.php'; + // can put formats for different dbs here + return NDate::now(); + } + + function convertTimeToUTC($field, $value) { + // make sure the value is there and doesn't equal "null" + // "null" is a special-case which gets changed to NULL in the sql + if ($value && $value != 'null') { + $table = $this->table(); + $def = $table[$field]; + // can put DB-specific date formatting here... + $format = '%Y-%m-%d %H:%M:%S'; + if (N_DAO_TIME & $def && !(N_DAO_DATE & $def)) { + // we can get away with strtotime() on time values (no date) + $value = date('Y-m-d H:i:s', strtotime($value)); + $format = '%H:%M:%S'; + } + include_once 'n_date.php'; + $rvalue = NDate::convertTimeToUTC($value, $format); + if (!$rvalue) { + // if it's false then pass it back or string nullify it (which is handled in insert/update) + $value = N_DAO_NOTNULL & $def?$value:'null'; + } else { + $value = $rvalue; + } + } + return $value; + } + + function _loadConfig() { + if (!empty($this->_config)) return; + if (ENVIRONMENT == 'production') { + // build the cache dir if it's not there + $cache_dir = CACHE_DIR . '/ntercache/db/'; + include_once 'n_filesystem.php'; + $dir_built = file_exists($cache_dir)?true:NFileSystem::buildDirs($cache_dir); + if ($dir_built) { + include_once 'Cache/Lite.php'; + $cache = new Cache_Lite(array('cacheDir'=>$cache_dir, 'automaticSerialization'=>true, 'lifeTime'=>60*60*24*7)); + if ($config = $cache->get($this->tableName(), 'tableconfig')) { + $this->_config = $config; + $this->debug('Loaded cached table config for: ' . $this->tableName()); + return; + } + } + } + $defs = $this->tableInfo($this->tableName(true)); + if (PEAR::isError($defs)) { + // TODO: raise error about table not existing. + $this->debug($defs->getMessage() . "\n" . $defs->getUserInfo(), N_DEBUGTYPE_INFO, PEAR_LOG_ERR); + return; + } + $db = &$this->_db; + $db->loadModule('Datatype'); + $fields = array(); + foreach ($defs as $def) { + if (is_array($def)) + $fields[$def['name']] = (object) $def; + } + foreach($fields as $field=>$def) { + $config = $this->getFieldType($field); + if ($config) { + $this->_config[$field] = $config; + } + } + $this->debug('Created table config for: ' . $this->tableName()); + if (ENVIRONMENT == 'production') { + $cache->save($this->_config, $this->tableName(), 'tableconfig'); + $this->debug('Cached table config for: ' . $this->tableName()); + } + } + + function getFieldType($field) { + if (is_object($field)) { + return false; + } + if (is_string($field)) { + $this->_db->loadModule('Reverse'); + $field_defs = $this->_db->reverse->getTableFieldDefinition($this->tableName(), $field); + if (PEAR::isError($field_defs)) { + // TODO: throw an error here + return false; + } + } + $type_map = array( + 'text' => N_DAO_STR, + 'boolean' => N_DAO_BOOL, + 'integer' => N_DAO_INT, + 'decimal' => N_DAO_FLOAT, + 'float' => N_DAO_FLOAT, + 'date' => N_DAO_DATE, + 'time' => N_DAO_TIME, + 'timestamp' => N_DAO_DATE + N_DAO_TIME, + 'clob' => N_DAO_TXT, + 'blob' => N_DAO_BLOB + ); + $n_type = 0; + $dsn = $this->_db->getDSN('array'); + $notnull_set = false; + foreach ($field_defs as $field_def) { + $type = $field_def['mdb2type']; + $length = isset($field_def['length'])?$field_def['length']:null; + $unsigned = isset($field_def['unsigned'])?$field_def['unsigned']:null; + $n_type += array_key_exists($type, $type_map)?$type_map[$type]:0; + $n_type += ($field_def['notnull'] && !($n_type & N_DAO_NOTNULL))?N_DAO_NOTNULL:0; + } + return $n_type; + } + + function tableInfo($result, $mode = null) { + $this->_db->loadModule('Reverse'); + $res = $this->_db->reverse->tableInfo($result, $mode); + return $res; + } + + /** + * Return a textual error message for a NModel error code + * + * @param integer error code + * + * @return string error message, or false if the error code was + * not recognized + */ + function errorMessage($value) { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + N_DAO_ERROR => 'unknown error', + N_DAO_OBJECT_NOT_FOUND => 'the object could not be found', + N_DAO_OBJECT_EXISTS => 'an object by that name already exists', + N_DAO_FILE_NOT_FOUND => 'the file could not be found', + N_DAO_CLASS_NOT_FOUND => 'the class could not be found', + N_DAO_CLASS_ERROR => 'unkown class error', + N_DAO_RECORD_EXISTS => 'an info object by that name already exists', + N_DAO_RECORD_NOT_FOUND => 'the class record could not be found' + ); + } + if (NModel::isError($value)) { + $value = $value->getCode(); + } + return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[IO_ERROR]; + } + + function isError($value) { + return (is_object($value) && + (get_class($value) == 'NModelError' || + is_subclass_of($value, 'NModelError'))); + } + + // utility functions + function debug($message, $debug_type = N_DEBUGTYPE_INFO, $log_level = PEAR_LOG_DEBUG, $ident=false) { + if (!$ident) { + $ident = (isset($this) && is_a($this, __CLASS__))?get_class($this):__CLASS__; + } + NDebug::debug($message, $debug_type, $log_level, $ident); + } +} + + +class NModelError extends PEAR_Error { + /** + * NModelError constructor. + * + * @param mixed NModel error code, or string with error message. + * @param integer what "error mode" to operate in + * @param integer what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param mixed additional debug info, such as the last query + * + * @access public + * + * @see PEAR_Error + */ + function Model_Error($code = N_DAO_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null) { + if (is_int($code)) { + $this->PEAR_Error('N_DAO Error: ' . NModel::errorMessage($code), $code, $mode, $level, $debuginfo); + } else { + $this->PEAR_Error("N_DAO Error: $code", N_DAO_ERROR, $mode, $level, $debuginfo); + } + } +} + +if (version_compare( phpversion(), "5") < 0) { + overload('NModel'); +} diff --git a/lib/n_object.php b/lib/n_object.php new file mode 100644 index 0000000..1844c51 --- /dev/null +++ b/lib/n_object.php @@ -0,0 +1,27 @@ +, Andy VanEe + * @copyright 2005-2014 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class Object { + function __construct(){} + + function toString() { + return get_class($this); + } +} diff --git a/lib/n_quickform.php b/lib/n_quickform.php new file mode 100644 index 0000000..43d6051 --- /dev/null +++ b/lib/n_quickform.php @@ -0,0 +1,600 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NQuickForm extends HTML_QuickForm { + function NQuickForm($formName='', $method='POST', $action='', $target='_self', $attributes=null) { + $this->HTML_QuickForm($formName, $method, $action, $target, $attributes); + $renderer =& $this->defaultRenderer(); + $renderer->setHeaderTemplate("\n\t\n\t\t
      {header}
      \n\t"); + $renderer->setElementTemplate("\n\t\n\t\t*{label}\n\t\t
      {error}
      \t{element}\n\t"); + if (CURRENT_SITE == 'admin') { + $this->setMaxFileSize(1024*1024*20); + if (strtolower($method) == 'get') { + if (isset($_GET['page_id']) && $_GET['page_id']) { + $this->setDefaults(array('page_id'=>$_GET['page_id'])); + $this->addElement('hidden', 'page_id'); + } + if (isset($_GET['edit']) && $_GET['edit']) { + $this->setDefaults(array('edit'=>$_GET['edit'])); + $this->addElement('hidden', 'edit'); + } + } + } + } + + function getEmailMsg() { + $msg = ''; + $msg .= ' HTTP User Agent: ' . $_SERVER['HTTP_USER_AGENT'] . "\n"; + $msg .= ' Website: http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] . "\n\n"; + $vals = $this->getSubmitValues(); + foreach ($this->_elementIndex as $element_name=>$order) { + $msg .= ' '; + $elem = &$this->getElement($element_name); + $type = $elem->getType(); + $label = $elem->getLabel(); + $label = is_array($label)?$label[0]:$label; + $val = isset($vals[$element_name])?$vals[$element_name]:null; + if (!$val) continue; + switch($type) { + case 'hidden': + break; + case 'html': + break; + case 'submit': + break; + case 'header': + $msg .= "\n ======================================================================\n"; + $msg .= ' ' . strip_tags($elem->_text) . "\n"; + $msg .= " ======================================================================\n\n"; + break; + case 'group': + $group_type = $elem->getGroupType(); + if ($group_type == 'radio') { + $msg .= $label?strip_tags($label) . ':':''; + foreach ($elem->_elements as $subelem) { + $subval = $subelem->getValue(); + if ($subval == $val) { + $msg .= strip_tags($subelem->_text) . "\n"; + break; + } + } + } else { + $msg .= $label?strip_tags($label) . "\n":''; + foreach ($elem->_elements as $subelem) { + $type = $subelem->getType(); + if ($type == 'checkbox') { + if (isset($val[$subelem->getName()])) { + $msg .= ($label?' ':'') . ($subelem->_text?$subelem->_text:$subelem->getValue()) . "\n"; + } + } else { + $msg .= ($label?' ':'') . ($subelem->_text?$subelem->_text:$subelem->getValue()) . "\n"; + } + } + } + break; + case 'select': + $val = is_array($val)?implode(', ', $val):$val; + $msg .= $label . ": " . $val . "\n"; + break; + case 'date': + $vars = array('w', 'd', 'n', 'Y', 'h', 'H', 'i', 's', 'a', 'A'); + $vars = array('D', 'l', 'd', 'M', 'm', 'F', 'Y', 'y', 'h', 'H', 'i', 's', 'a', 'A'); + $tmp = ''; + for ($i = 0, $length = strlen($elem->_options['format']); $i < $length; $i++) { + $sign = $elem->_options['format']{$i}; + $tmp .= in_array($sign, $vars)?$val[$sign]:$sign; + } + $val = date($elem->_options['format'], strtotime($tmp)); + $msg .= $label . ": " . $tmp . "\n"; + break; + case 'textarea': + $msg .= $label . ":\n " . $val . "\n"; + break; + default: + $msg .= $label . ": " . $val . "\n"; + break; + } + } + return wordwrap($msg); + } + + function getInsertSQL($table) { + include_once 'n_db.php'; + $db = &NDB::connect(); + $tablefields = $db->tableInfo($table); + + $vals = $this->getSubmitValues(); + $fields = ''; + $values = ''; + foreach ($tablefields as $tablefield) { + $field_name = $tablefield['name']; + $field_type = $tablefield['type']; + $field_len = $tablefield['len']; + if (isset($vals[$field_name])) { + if ($fields != '') $fields .= ','; + $fields .= $db->quoteIdentifier($field_name); + if ($values != '') $values .= ','; + + $elem = &$this->getElement($field_name); + $type = $elem->getType(); + $val = $vals[$field_name]; + $val = !get_magic_quotes_gpc()?addslashes($val):$val; + switch($type) { + case 'submit': + break; + case 'header': + break; + case 'group': + $tmp = ''; + $group_type = $elem->getGroupType(); + if ($group_type == 'radio') { + foreach ($elem->_elements as $subelem) { + $subval = $subelem->getValue(); + if ($subval == $val) { + if ($tmp != '') $tmp .= ', '; + $tmp .= strip_tags($subelem->_text); + break; + } + } + } else { + foreach ($elem->_elements as $subelem) { + $type = $subelem->getType(); + if ($type == 'checkbox') { + if (isset($val[$subelem->getName()])) { + if ($tmp != '') $tmp .= ', '; + $tmp .= ($subelem->_text?$subelem->_text:$subelem->getValue()); + } + } else { + if ($tmp != '') $tmp .= ', '; + $tmp .= ($label?' ':'') . ($subelem->_text?$subelem->_text:$subelem->getValue()); + } + } + } + $values .= $db->quoteSmart($tmp); + break; + case 'select': + $val = is_array($val)?implode(', ', $val):$val; + $values .= $db->quoteSmart($val); + break; + case 'date': + $vars = array('w', 'd', 'n', 'Y', 'h', 'H', 'i', 's', 'a', 'A'); + $vars = array('D', 'l', 'd', 'M', 'm', 'F', 'Y', 'y', 'h', 'H', 'i', 's', 'a', 'A'); + $tmp = ''; + for ($i = 0, $length = strlen($elem->_options['format']); $i < $length; $i++) { + $sign = $elem->_options['format']{$i}; + $tmp .= in_array($sign, $vars)?$val[$sign]:$sign; + } + if ($field_type == 'date') { + $format = 'Y-m-d'; + } else if ($field_type == 'datetime') { + $format = 'Y-m-d H:i:s'; + } else if ($field_type == 'time') { + $format = 'H:i:s'; + } else { + $format = $elem->_options['format']; + } + $val = date($format, strtotime($tmp)); + $values .= $db->quoteSmart($tmp); + break; + default: + $values .= $db->quoteSmart($val); + break; + } + } + } + if ($fields != '' && $values != '') { + foreach ($tablefields as $tablefield) { + if ($tablefield['name'] == 'ip' && !isset($vals[$tablefield['ip']])) { + $fields .= ',' . $db->quoteIdentifier('ip'); + $values .= ',' . $db->quoteSmart($_SERVER['REMOTE_ADDR']); + } + if ($tablefield['name'] == 'inserted' && !isset($vals[$tablefield['name']])) { + $fields .= ',' . $db->quoteIdentifier('inserted'); + $values .= ',NOW()'; + } + } + } + $sql = 'INSERT INTO ' . $db->quoteIdentifier($table) . " ($fields) VALUES ($values)"; + + return $sql; + } +} + +require_once 'HTML/QuickForm/static.php'; +/** + * A pseudo-element used for adding alerts to form + * + * @author Tim Glen + * @access public + */ +class HTML_QuickForm_cmsalert extends HTML_QuickForm_static +{ + function HTML_QuickForm_cmsalert($elementName = null, $text = null) { + $this->HTML_QuickForm_static($elementName, null, $text); + $this->_type = 'cmserror'; + } + + function toHTML() { + return $this->_getTabs() . "\n\t\n\t\t
      " . $this->_text . "
      \n\t"; + } +} +/** + * A pseudo-element used for adding errors to form + * + * @author Tim Glen + * @access public + */ +class HTML_QuickForm_cmserror extends HTML_QuickForm_static +{ + function HTML_QuickForm_cmserror($elementName = null, $text = null) { + $this->HTML_QuickForm_static($elementName, null, $text); + $this->_type = 'cmserror'; + } + + function toHTML() { + return $this->_getTabs() . "\n\t\n\t\t
      " . $this->_text . "
      \n\t"; + } +} + +require_once 'HTML/QuickForm/group.php'; +require_once 'HTML/QuickForm/file.php'; +require_once 'HTML/QuickForm/link.php'; +require_once 'HTML/QuickForm/hidden.php'; +require_once 'HTML/QuickForm/checkbox.php'; +require_once 'HTML/QuickForm/CAPTCHA.php'; +class HTML_QuickForm_CMS_file extends HTML_QuickForm_group +{ + + var $file = ''; + var $_options = array('upload_dir'=>''); + + /** + * These complement separators, they are appended to the resultant HTML + * @access private + * @var array + */ + var $_value = null; + var $_elements = array(); + var $_wrap = array('', ''); + var $_separator = '
      '; + + var $elementName = ''; + var $elementLabel = ''; + + + // }}} + // {{{ constructor + + /** + * Class constructor + * + * @access public + * @param string Element's name + * @param mixed Label(s) for an element + * @param array Options to control the element's display + * @param mixed Either a typical HTML attribute string or an associative array + */ + function HTML_QuickForm_CMS_file($elementName = null, $elementLabel = null, $options = array(), $attributes = null) { + $this->HTML_QuickForm_element($elementName, $elementLabel, $attributes); + $this->_persistantFreeze = true; + $this->_appendName = false; + $this->_type = 'cms_file'; + // set the options, do not bother setting bogus ones + if (is_string($options)) { + $this->file = $options; + } else if (is_array($options)) { + if (isset($options['file'])) { + $this->file = $options['file']; + unset($options['file']); + } + foreach ($options as $name => $value) { + if (isset($this->_options[$name])) { + if (is_array($value)) { + $this->_options[$name] = @array_merge($this->_options[$name], $value); + } else { + $this->_options[$name] = $value; + } + } + } + $this->_options['upload_dir'] = empty($this->_options['upload_dir'])?UPLOAD_DIR:preg_replace('|/$|', '', $this->_options['upload_dir']); + } + } + + // }}} + // {{{ _createElements() + + function _createElements() { + if ($this->getValue()) { + include_once 'n_filesystem.php'; + if (file_exists($_SERVER['DOCUMENT_ROOT'] . $this->getValue())) { + $filesize = NFilesystem::filesize_format(filesize($_SERVER['DOCUMENT_ROOT'] . $this->getValue())); + $this->_elements[] =& new HTML_QuickForm_hidden($this->getName() . '__current', $this->getValue()); + $this->_elements[] =& new HTML_QuickForm_link(null, 'Current File', $this->getValue(), $this->getValue() . ($filesize?' (' . $filesize . ')':''), array('target'=>'_blank')); + $this->_elements[] =& new HTML_QuickForm_checkbox($this->getName() . '__remove', null, 'Remove File', $this->getAttributes()); + } else { + $this->setValue(false); + } + } + $this->_elements[] =& new HTML_QuickForm_file($this->getName(), $this->getLabel(), $this->_options, $this->getAttributes()); + } + + // }}} + // {{{ setValue() + + function setValue($value) { + $this->_value = $value; + } + + // }}} + // {{{ getValue() + + function getValue() { + return $this->_value; + } + + // }}} + // {{{ toHtml() + + function toHtml() { + include_once('HTML/QuickForm/Renderer/Default.php'); + $renderer =& new HTML_QuickForm_Renderer_Default(); + $renderer->setElementTemplate($this->_wrap[0] . '{element}' . $this->_wrap[1]); + // $renderer->setGroupElementTemplate('{element}', $this->getName()); + parent::accept($renderer); + return $renderer->toHtml(); + } + + // }}} + // {{{ accept() + + function accept(&$renderer, $required = false, $error = null) { + $renderer->renderElement($this, $required, $error); + } + + // }}} + // {{{ onQuickFormEvent() + + /** + * Called by HTML_QuickForm whenever form event is made on this element + * + * @param string Name of event + * @param mixed event arguments + * @param object calling object + * @since 1.0 + * @access public + * @return bool + */ + function onQuickFormEvent($event, $arg, &$caller) + { + switch ($event) { + case 'updateValue': + if ($caller->getAttribute('method') == 'get') { + return PEAR::raiseError('Cannot add a file upload field to a GET method form'); + } + HTML_QuickForm_element::onQuickFormEvent($event, $arg, $caller); + $caller->updateAttributes(array('enctype' => 'multipart/form-data')); + $caller->setMaxFileSize(); + break; + case 'addElement': + $this->onQuickFormEvent('createElement', $arg, $caller); + return $this->onQuickFormEvent('updateValue', null, $caller); + break; + case 'createElement': + $className = get_class($this); + $this->$className($arg[0], $arg[1], $arg[2]); + break; + } + return true; + } // end func onQuickFormEvent + + // }}} + // {{{ _findValue() + + /** + * Tries to find the element value from the values array + * + * Needs to be redefined here as $_FILES is populated differently from + * other arrays when element name is of the form foo[bar] + * + * @access private + * @return mixed + */ + function _findValue(&$values) { + $elementName = $this->getName(); + if (empty($_FILES) && isset($values[$elementName]) && $values[$elementName]) { + return $values[$elementName]; + } + if (empty($_FILES)) { + return null; + } + if (isset($_FILES[$this->getName()])) { + if ($_FILES[$this->getName()]['error']) { + // return $caller->_defaultValues[$this->getName()]; + } + return $_FILES[$elementName]; + } elseif (false !== ($pos = strpos($elementName, '['))) { + $base = substr($elementName, 0, $pos); + $idx = "['" . str_replace(array(']', '['), array('', "']['"), substr($elementName, $pos + 1, -1)) . "']"; + $props = array('name', 'type', 'size', 'tmp_name', 'error'); + $code = "if (!isset(\$_FILES['{$base}']['name']{$idx})) {\n" . + " return null;\n" . + "} else {\n" . + " \$value = array();\n"; + foreach ($props as $prop) { + $code .= " \$value['{$prop}'] = \$_FILES['{$base}']['{$prop}']{$idx};\n"; + } + return eval($code . " return \$value;\n}\n"); + // } else if (isset($cakll)) { + + } else { + return null; + } + } + + // }}} + + // {{{ moveUploadedFile() + + /** + * Moves an uploaded file into the destination + * + * @param string Destination directory path + * @param string New file name + * @access public + */ + function moveUploadedFile($dest, $fileName = '') + { + if ($dest != '' && substr($dest, -1) != '/') { + $dest .= '/'; + } + $fileName = ($fileName != '') ? $fileName : basename($this->_value['name']); + if (move_uploaded_file($this->_value['tmp_name'], $dest . $fileName)) { + return true; + } else { + return false; + } + } // end func moveUploadedFile + + // }}} + // {{{ isUploadedFile() + + /** + * Checks if the element contains an uploaded file + * + * @access public + * @return bool true if file has been uploaded, false otherwise + */ + function isUploadedFile() + { + return $this->_ruleIsUploadedFile($this->_value); + } // end func isUploadedFile + + // }}} + // {{{ _ruleIsUploadedFile() + + /** + * Checks if the given element contains an uploaded file + * + * @param array Uploaded file info (from $_FILES) + * @access private + * @return bool true if file has been uploaded, false otherwise + */ + function _ruleIsUploadedFile($elementValue) + { + if ((isset($elementValue['error']) && $elementValue['error'] == 0) || + (!empty($elementValue['tmp_name']) && $elementValue['tmp_name'] != 'none')) { + return is_uploaded_file($elementValue['tmp_name']); + } else { + return false; + } + } // end func _ruleIsUploadedFile + + // }}} + // {{{ _ruleCheckMaxFileSize() + + /** + * Checks that the file does not exceed the max file size + * + * @param array Uploaded file info (from $_FILES) + * @param int Max file size + * @access private + * @return bool true if filesize is lower than maxsize, false otherwise + */ + function _ruleCheckMaxFileSize($elementValue, $maxSize) + { + if (!HTML_QuickForm_file::_ruleIsUploadedFile($elementValue)) { + return true; + } + return ($maxSize >= @filesize($elementValue['tmp_name'])); + } // end func _ruleCheckMaxFileSize + + // }}} + // {{{ _ruleCheckMimeType() + + /** + * Checks if the given element contains an uploaded file of the right mime type + * + * @param array Uploaded file info (from $_FILES) + * @param mixed Mime Type (can be an array of allowed types) + * @access private + * @return bool true if mimetype is correct, false otherwise + */ + function _ruleCheckMimeType($elementValue, $mimeType) + { + if (!HTML_QuickForm_file::_ruleIsUploadedFile($elementValue)) { + return true; + } + if (is_array($mimeType)) { + return in_array($elementValue['type'], $mimeType); + } + return $elementValue['type'] == $mimeType; + } // end func _ruleCheckMimeType + + // }}} + // {{{ _ruleCheckFileName() + + /** + * Checks if the given element contains an uploaded file of the filename regex + * + * @param array Uploaded file info (from $_FILES) + * @param string Regular expression + * @access private + * @return bool true if name matches regex, false otherwise + */ + function _ruleCheckFileName($elementValue, $regex) + { + if (!HTML_QuickForm_file::_ruleIsUploadedFile($elementValue)) { + return true; + } + return preg_match($regex, $elementValue['name']); + } // end func _ruleCheckFileName + + // }}} + // {{{ _ruleCheckRemove() + + /** + * Checks if the removed element should be uploaded + * + * @param values All values + * @access private + * @return bool true if remove, array of errors otherwise + */ + function _ruleCheckRemove($values) + { + $errors = array(); + if (isset($values['media_file__remove'])) { + $errors['media_file'] = 'Media File is a required field'; + } + return empty($errors)?true:$errors; + } // end func _ruleCheckRemove + + // }}} + +} + +NQuickForm::registerElementType('cmserror', 'n_quickform.php', 'HTML_QuickForm_cmserror'); +NQuickForm::registerElementType('cmsalert', 'n_quickform.php', 'HTML_QuickForm_cmsalert'); +NQuickForm::registerElementType('cms_file', 'controller/form.php', 'HTML_QuickForm_CMS_file'); +NQuickForm::registerElementType('foreignkey', 'controller/form.php', 'HTML_QuickForm_foreignkey'); +NQuickForm::registerElementType('fckeditor', 'controller/form.php', 'HTML_QuickForm_fckeditor'); +//NQuickForm::registerElementType('CAPTCHA_Image', 'n_quickform.php', 'HTML_QuickForm_captcha'); +// NQuickForm::registerElementType('nterchange_date', 'HTML/QuickForm/nterchange_date.php', 'HTML_QuickForm_nterchange_date'); +?> diff --git a/lib/n_server.php b/lib/n_server.php new file mode 100644 index 0000000..b83ae44 --- /dev/null +++ b/lib/n_server.php @@ -0,0 +1,64 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NServer extends Object { + /** + * Returns the REQUEST_URI from the server environment, or, failing that, + * constructs a new one, using the PHP_SELF constant and other variables. + * + * @return string URI + */ + static function setUri() { + if (NServer::env('REQUEST_URI')) { + $uri = NServer::env('REQUEST_URI'); + } else { + if (NServer::env('argv')) { + $uri = NServer::env('argv'); + $uri = NServer::env('PHP_SELF') .'/'. $uri[0]; + } else { + $uri = NServer::env('PHP_SELF') .'/'. NServer::env('QUERY_STRING'); + } + } + return $uri; + } + + static function env($key) { + if (isset($_SERVER[$key])) { + return $_SERVER[$key]; + } else if (isset($_ENV[$key])) { + return $_ENV[$key]; + } else if (getenv($key) !== false) { + return getenv($key); + } + if ($key == 'DOCUMENT_ROOT') { + $offset = 0; + if (!strpos(NServer::env('SCRIPT_NAME'), '.php')) { + $offset = 4; + } + return substr(NServer::env('SCRIPT_FILENAME'), 0, strlen(NServer::env('SCRIPT_FILENAME')) - (strlen(NServer::env('SCRIPT_NAME')) + $offset)); + } + if ($key == 'PHP_SELF') { + return r(NServer::env('DOCUMENT_ROOT'), '', NServer::env('SCRIPT_FILENAME')); + } + return null; + } +} +?> diff --git a/lib/n_view.php b/lib/n_view.php new file mode 100644 index 0000000..ed57414 --- /dev/null +++ b/lib/n_view.php @@ -0,0 +1,415 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class NView extends Smarty { + // variables inherited from the controller + var $controller = null; + var $name = null; + var $action = null; + var $flash = null; + var $page_title = null; + var $_view_assigns = array(); + var $view_cache_name = null; + + var $helper = null; + var $url = ''; + + function NView(&$controller) { + if (is_object($controller)) { + $this->controller = &$controller; + $this->name = $controller->name; + $this->action = $this->controller->action; + $this->flash = &$this->controller->flash; + $this->page_title = $this->controller->page_title; + $this->_view_assigns = &$this->controller->_view_assigns; + } else { + $this->name = $controller; + } + + // Smarty setup + $this->Smarty(); + if (is_object($controller)) { + // Find templates in /app/views directory, first checking front-end, then back-end + $this->template_dir = array($controller->base_view_dir . '/app/views', $controller->base_view_dir . '/vendor/nterchange/app/views'); + // $this->template_dir = $controller->base_view_dir . '/app/views'; + } + if (is_dir(BASE_DIR . '/vendor/SmartyPaginate/plugins')) { + $this->plugins_dir[] = BASE_DIR . '/vendor/SmartyPaginate/plugins'; + } + if (defined('SMARTY_CACHE_HANDLER')) { + $this->cache_handler_func = SMARTY_CACHE_HANDLER . '_cache_handler'; + } + $this->compile_dir = CACHE_DIR . '/templates_c'; + $this->cache_dir = CACHE_DIR . '/smarty_cache'; + $this->helper = new ViewHelper($this); + register_shutdown_function(array(&$this, '__destruct')); + + $this->register_outputfilter(array(&$this->helper, 'convertInternalLink')); + $this->register_function('call', array(&$this, 'call')); + $this->register_function('assign_encoded', array(&$this, 'assignURLEncoded')); + $this->register_block('dynamic', array(&$this, 'dynamic'), false); + $this->register_block('form', array(&$this, 'block_form'), false); + $this->register_block('table', array(&$this, 'block_table'), false); + $this->register_function('tableize', array(&$this, 'function_tableize'), false); + // Shows more debug information is development is the active mode. + // For more information in your templates add {debug} anywhere. + if (defined('SMARTY_DEBUG') && SMARTY_DEBUG) { + $this->debugging = true; + } + } + + function &singleton(&$controller) { + static $instances = array(); + // if $controller is not an object, just use the string + $name = is_object($controller)?$controller->name:$controller; + if (!isset($instances[$name])) { + $instances[$name] = new NView($controller); + } + if (is_object($controller)) { + $instances[$name]->_view_assigns = &$controller->_view_assigns; + $instances[$name]->caching = $controller->view_caching?2:0; + $instances[$name]->cache_lifetime = $controller->view_cache_lifetime; + $instances[$name]->view_cache_name = $controller->view_cache_name; + $instances[$name]->template_dir = $controller->base_view_dir . '/app/views'; + } + return $instances[$name]; + } + + function render($options=array(), $added_assigns=array()) { + if (is_string($options)) { + $options = array('action'=>$options); + } + if (isset($options['status'])) { + require_once 'HTTP/Header.php'; + $http = new HTTP_Header; + $http->sendStatusCode($options['status']); + unset($http); + } + if (isset($options['nothing']) && $options['nothing']) { + return ''; + } + if (isset($options['text'])) { + $out = $options['text']; + if ($options['layout']) { + $this->renderLayout($options['layout'], $out); + } + return $out; + } + $return = isset($options['return'])?(bool) $options['return']:false; + + // non-default action might be passed + $action = isset($options['action'])?$options['action']:$this->action; + + $filename = null; + + // look for specific file + if (isset($options['file'])) { + $filename = $options['file']; + } + if (!isset($filename) || !is_file($filename)) { + // get the default filename + $filename = $this->_getViewFileName(Inflector::underscore($action)); + // var_dump($filename); + } + if (!$filename && !isset($options['layout'])) { + // TODO: raise an error here - no file exists for the action/file specified + return; + } + $out = ''; + if ($filename) { + $this->assign($this->_view_assigns); + if (!$this->isCached($options)) { + $this->assign('_EXTERNAL_CACHE_', defined('EXTERNAL_CACHE') && constant('EXTERNAL_CACHE')?EXTERNAL_CACHE:false); + $this->assign($added_assigns); + } + $out = $this->fetch($filename, $this->view_cache_name); + } + + // check for a layout and render if one was passed + if (isset($options['layout']) && (!$this->controller || (!isset($this->controller->params['bare']) || !$this->controller->params['bare']))) { + $out = $this->renderLayout($options['layout'], $out, null, $return); + } + + // return $out or print it + if ($return) { + return $out; + } + print $out; + } + + function renderLayout($layout, $main_content, $sidebar_content=null, $return=false) { + $filename = $this->_getLayoutFileName($layout); + if (!$this->isCachedLayout($layout)) { + // assign everything else just in case... + $this->assign($this->_view_assigns); + if (!$main_content) { + $main_content = $this->get_template_vars('MAIN_CONTENT'); + } + if (!$sidebar_content) { + $sidebar_content = $this->get_template_vars('SIDEBAR_CONTENT'); + } + $flash = array(); + if (is_object($this->flash)) { + foreach ($this->flash->flashes as $key=>$val) { + $flash[$key] = $val; + } + } + // assign anything passed explicitly + $this->assign(array('MAIN_CONTENT'=>$main_content, 'SIDEBAR_CONTENT'=>$sidebar_content, '_TITLE_'=>$this->page_title, '_FLASH_'=>$flash)); + } + $out = $this->fetch($filename, $this->view_cache_name); + if ($return) return $out; + print $out; + } + + function isCached($options=array()) { + if (is_string($options)) { + $options = array('action'=>$options); + } + // non-default action might be passed + $action = isset($options['action'])?$options['action']:$this->action; + // look for specific file + if (isset($options['file'])) { + $filename = $options['file']; + } + if (!isset($filename) || !is_file($filename)) { + // get the default filename + $filename = $this->_getViewFileName($action); + } + if (!$filename && !isset($options['layout'])) { + // TODO: raise an error here - no file exists for the action/file specified + return false; + } + return $this->is_cached($filename, $this->view_cache_name); + } + function clearCache($options=array()) { + if ($this->isCached($options)) { + if (is_string($options)) { + $options = array('action'=>$options); + } + // non-default action might be passed + $action = isset($options['action'])?$options['action']:$this->action; + // look for specific file + if (isset($options['file'])) { + $filename = $options['file']; + } + if (!isset($filename) || !is_file($filename)) { + // get the default filename + $filename = $this->_getViewFileName($action); + } + if (!$filename && !isset($options['layout'])) { + // TODO: raise an error here - no file exists for the action/file specified + return false; + } + return $this->clear_cache($filename, $this->view_cache_name); + } + return false; + } + + function isCachedLayout($layout) { + $filename = $this->_getLayoutFileName($layout); + if (!$filename) { + return false; + } + return $this->is_cached($filename, $this->view_cache_name); + } + function clearLayoutCache($layout) { + if ($this->isCachedLayout($layout)) { + $filename = $this->_getLayoutFileName($layout); + if (!$filename) { + return false; + } + return $this->clear_cache($filename, $this->view_cache_name); + } + return true; + } + + function call($params, &$view) { + $controller = isset($params['controller'])?$params['controller']:false; + $action = isset($params['action'])?$params['action']:false; + if (!$controller || !$action) { + return; + } + unset($params['controller']); + unset($params['action']); + include_once 'controller/inflector.php'; + $ctrl = &NController::factory($controller); + if ($controller) { + $method = Inflector::camelize($action); + if (is_callable(array($ctrl, $method))) { + // pass all currently variables along + // $ctrl->set($view->get_template_vars()); + return $ctrl->$method($params, $view); + } + } + return ''; + } + + function assignURLEncoded($params, &$view) { + $var = isset($params['var'])?$params['var']:false; + $value = isset($params['value'])?$params['value']:false; + if (!$var || !$value) { + return; + } + unset($params['var']); + unset($params['value']); + $view->assign($var, urlencode($value)); + } + + function dynamic($param, $content, &$view) { + return $content; + } + + function block_form($params, $content, &$smarty, &$repeat){ + $form = array_key_exists('for', $params) ? $params['for'] : false; + if ( !$content ){ + $elements = array(); + if (!array_key_exists('sections', $form)) { return; } + foreach ($form['sections'][0]['elements'] as $elem) $elements[$elem['name']] = $elem; + foreach ($form['errors'] as $k=>$err) $elements[$k]['error'] = $err; + foreach ($elements as $k=>$v) $smarty->assign($k, $v); + } else { + return "
      ".$content.$form['javascript']."
      "; + } + } + + function block_table($params, $content, &$smarty, &$repeat){ + if (!isset($this->block_count)) $this->block_count = 0; + else $this->block_count += 1; + + if ($this->block_count == 0){ $smarty->assign('table_header', true); } + else { $smarty->assign('table_header', false); } + $arr = array_key_exists('for', $params) ? $params['for'] : false; + $class = array_key_exists('class', $params) ? $params['class'] : ""; + $smarty->assign('row', $arr[$this->block_count]); + $repeat = (array_key_exists($this->block_count, $arr)); + $header = ($this->block_count == 1) ? "\n":""; + $footer = ($repeat) ? "": "
      "; + return $header.$content.$footer; + } + + function function_tableize($params, &$view){ + require_once('lib/controller/inflector.php'); + $arr = array_key_exists('collection', $params) ? $params['collection'] : false; + $trim_arg = array_key_exists('trim', $params) ? $params['trim'] : false; + $trim_length = ($trim_arg) ? intval($trim_arg) : false; + $keys = array_keys($arr[0]); + $html = "\n"; + + $html .= "\t\n"; + foreach($keys as $key){ + $key = (substr($key, 0, 4) == "cms_") ? substr($key, 4) : $key; + $key = Inflector::humanize($key); + $html .= "\t\t\n"; + } + $html .= "\t\n"; + + foreach ($arr as $row){ + $html .= "\t\n"; + foreach($keys as $key){ + if (is_array($row[$key])) { + $value = print_r($row[$key], true); + } else { + $value = strip_tags($row[$key]); + } + if ($trim_length && strlen($value) > $trim_length) $value = substr($value, 0, $trim_length).".."; + $html .= "\t\t\n"; + } + $html .= "\t\n"; + } + $html .= "
      $key
      $value
      \n"; + return $html; + } + + + function _getViewFileName($action) { + $filename = null; + if (file_exists($this->template_dir . '/' . $this->name . '/' . $action . '.html')) { + $filename = $this->template_dir . '/' . $this->name . '/' . $action . '.html'; + } else if (file_exists(BASE_DIR . '/app/views/' . $this->name . '/' . $action . '.html')) { + $filename = BASE_DIR . '/app/views/' . $this->name . '/' . $action . '.html'; + } else if (file_exists(BASE_DIR . '/app/views/templates/' . $action . '.html')) { + $this->template_dir = BASE_DIR . '/app/views/'; + $filename = $this->template_dir . '/templates/' . $action . '.html'; + } else if (file_exists(BASE_DIR . '/app/views/templates/errors/' . $action . '.html')) { + $this->template_dir = BASE_DIR . '/app/views/'; + $filename = $this->template_dir . '/templates/errors/' . $action . '.html'; + // Look in the front end if all else fails. + } else if (file_exists(ASSET_DIR . '/views/' . $this->name . '/' . $action . '.html')) { + $filename = ASSET_DIR . '/views/' . $this->name . '/' . $action . '.html'; + } + return $filename; + } + + /** + * $view->_getLayoutFileName + * + * Resolves the path to the layout file in the following order: + * 1. Relative to application root /app/views/layouts/$layout.html + * 2. Relative to $this->template_dir + * 3. Relative to nterchange root /vendor/nterchange/app/views/layouts/$layout.html + */ + + function _getLayoutFileName($layout) { + $root_path = ROOT_DIR . "/app/views/layouts/$layout.html"; + $template_path = $this->template_dir . "/layouts/$layout.html"; + $base_path = BASE_DIR . "/app/views/layouts/$layout.html"; + + if (file_exists($root_path)) { return $root_path; } + else if (file_exists($template_path)) { return $template_path; } + else if (file_exists($base_path)) { return $base_path; } + } + + function __destruct() { + unset($this->helper); + } + + // utility functions + function debug($message, $debug_type = N_DEBUGTYPE_INFO, $log_level = PEAR_LOG_DEBUG, $ident=false) { + if (!$ident) { + $ident = (isset($this) && is_a($this, __CLASS__))?get_class($this):__CLASS__; + } + NDebug::debug($message, $debug_type, $log_level, $ident); + } + + // This function is called by Smarty templates when including a file. + // It looks at the file path and appends .html if neccessary and prepends page/ if no folder is specified. + // It will first check if a file exists at app/views, or at vendor/nterchange/app/views and then + // update the file path to its full absolute path before calling the original Smarty include function. + function _smarty_include($params) { + $filename = '/app/views/' . $params['smarty_include_tpl_file'] . '.html'; + $filename = str_replace(array('//','.html.html'), array('/','.html'), $filename); + + if (file_exists(ROOT_DIR . $filename)) { + $params['smarty_include_tpl_file'] = ROOT_DIR . $filename; + } else if (file_exists(BASE_DIR . $filename)) { + $params['smarty_include_tpl_file'] = BASE_DIR . $filename; + } else if (file_exists(ROOT_DIR . 'page/' . $filename)) { + $params['smarty_include_tpl_file'] = ROOT_DIR. 'page/' . $filename; + } else if (file_exists(BASE_DIR . 'page/' . $filename)) { + $params['smarty_include_tpl_file'] = BASE_DIR. 'page/' . $filename; + } + + parent::_smarty_include($params); + } +} +?> diff --git a/lib/view/helper.php b/lib/view/helper.php new file mode 100644 index 0000000..5156293 --- /dev/null +++ b/lib/view/helper.php @@ -0,0 +1,165 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class ViewHelper { + function ViewHelper(&$view) { + include_once 'helpers/tag_helper.php'; + include_once 'helpers/asset_tag_helper.php'; + include_once 'helpers/url_helper.php'; + include_once 'helpers/javascript_helper.php'; + include_once 'helpers/date_helper.php'; + include_once 'helpers/filesystem_helper.php'; + include_once 'helpers/search_helper.php'; + include_once 'helpers/action_track_helper.php'; + $asset_tag = new AssetTagHelper; + $tag = new TagHelper; + $url = new urlHelper; + $js = new JavascriptHelper; + $date = new DateHelper; + $search = new SearchHelper; + $filesystem = new FilesystemHelper; + $action_track = new ActionTrackHelper; + $view->register_modifier('humanize', array('Inflector', 'humanize')); + $view->register_modifier('camelize', array('Inflector', 'camelize')); + $view->register_modifier('image_tag', array(&$asset_tag, 'imageTag')); + $view->register_function('image_tag', array(&$asset_tag, 'imageTagFunc')); + $view->register_function('image_size', array(&$asset_tag, 'imageSizeFunc')); + $view->register_function('image_width', array(&$asset_tag, 'imageWidthFunc')); + $view->register_function('image_height', array(&$asset_tag, 'imageHeightFunc')); + $view->register_function('css_img_size', array(&$asset_tag, 'cssImageSizeFunc')); + $view->register_function('stylesheet_link_tag', array(&$asset_tag, 'stylesheetLinkTagFunc')); + $view->register_function('javascript_include_tag', array(&$asset_tag, 'javascriptIncludeTagFunc')); + $view->register_function('persistent_url', array(&$asset_tag, 'persistentUrl')); + $view->register_function('link_to', array(&$url, 'linkToFunc')); + $view->register_function('url_for', array(&$url, 'urlForFunc')); + $view->register_function('link_to_remote', array(&$js, 'linkToRemoteFunc')); + $view->register_modifier('date_format_local', array(&$date, 'dateFormatLocal')); + $view->register_modifier('close_tags', array(&$tag, 'closeTags')); + $view->register_modifier('filesize_format', array(&$filesystem, 'filesizeFormat')); + $view->register_modifier('download_time', array(&$filesystem, 'downloadTime')); + $view->register_function('download_icon', array(&$filesystem, 'downloadIcon')); + $view->register_function('search_field_list_select', array(&$search, 'searchFieldListSelect')); + $view->register_function('asset_edit_status', array(&$action_track, 'assetEditStatus')); + // now do the auto-load thing + $this->_autoLoadHelpers($view); + } + + function convertInternalLink($tpl_output, &$smarty) { + return urlHelper::convertInternalLink($tpl_output, $smarty); + } + + function _autoLoadHelpers(&$view) { + $controller = &$view->controller; + $helper_class = Inflector::camelize($controller->name) . 'Helper'; + if ($path = $this->_getIncludePath($controller->name)) { + include_once $path; + $controller->debug('Loaded helper file at ' . $path . " for $controller->name.", N_DEBUGTYPE_ASSET); + if (!class_exists($helper_class)) { + $controller->debug('Loaded helper file but could not find class for "' . $controller->name . '" controller', N_DEBUGTYPE_ASSET, PEAR_LOG_NOTICE); + return false; + } + $helper = new $helper_class; + $this->_loadHelperPlugins($view, $helper); + } + } + + /** + * Finds the path where the controller lives + * + * @param string $controller - should be an underscored word + * @return string + */ + function _getIncludePath($controller) { + $file = false; + $path = '%s/app/helpers/%s_helper.php'; + if (file_exists(sprintf($path, ROOT_DIR, $controller))) { + return sprintf($path, ROOT_DIR, $controller); + } else if (file_exists(sprintf($path, BASE_DIR, $controller))) { + return sprintf($path, BASE_DIR, $controller); + } else if (preg_match('/^' . APP_DIR . '/', $controller)) { + if (APP_DIR == $controller) { + return sprintf($path, BASE_DIR, 'nterchange/' . $controller); + } else { + $helper_file = preg_replace('/^' . APP_DIR . '_/', '', $controller); + if (file_exists(sprintf($path, BASE_DIR, 'nterchange/' . $helper_file))) { + return sprintf($path, BASE_DIR, 'nterchange/' . $helper_file); + } + } + } + $this->debug('Could not find helper file for "' . $controller . '" controller', N_DEBUGTYPE_ASSET, PEAR_LOG_NOTICE); + return false; + } + + function _loadHelperPlugins(&$view, &$helper) { + $plugin_types = array('function', 'modifier', 'block', 'compiler', 'prefilter', 'postfilter', 'outputfilter', 'resource', 'insert'); + $methods = get_class_methods(get_class($helper)); + foreach ($methods as $method) { + $matches = array(); + preg_match('/^([^_]+)/', $method, $matches); + $plugin_type = isset($matches[1]) && $matches[1]?strtolower($matches[1]):false; + if (!$plugin_type) { // this isn't a plugin function + continue; + } + if (in_array($plugin_type, $plugin_types)) { + $load_method = '_load' . ucwords($plugin_type); + $this->$load_method($view, $helper, $method); + } + } + } + + function _createPluginName($method, $plugin_type) { + return preg_replace('/^' . $plugin_type . '_/', '', $method); + } + + function _isPluginCacheable(&$helper, $method) { + return isset($helper->cacheable) && in_array($method, $helper->cacheable)?$helper->cacheable[$method]:null; + } + + function _loadFunction(&$view, &$helper, $method) { + $view->register_function($this->_createPluginName($method, 'function'), array(&$helper, $method), $this->_isPluginCacheable($helper, $method)); + } + function _loadModifier(&$view, &$helper, $method) { + $view->register_modifier($this->_createPluginName($method, 'modifier'), array(&$helper, $method)); + } + function _loadBlock(&$view, &$helper, $method) { + $view->register_block($this->_createPluginName($method, 'block'), array(&$helper, $method), $this->_isPluginCacheable($helper, $method)); + } + function _loadCompiler(&$view, &$helper, $method) { + $view->register_compiler_function($this->_createPluginName($method, 'compiler'), array(&$helper, $method), $this->_isPluginCacheable($helper, $method)); + } + function _loadPrefilter(&$view, &$helper, $method) { + $view->register_prefilter(array(&$helper, $method)); + } + function _loadPostfilter(&$view, &$helper, $method) { + $view->register_postfilter(array(&$helper, $method)); + } + function _loadOutputfilter(&$view, &$helper, $method) { + $view->register_outputfilter(array(&$helper, $method)); + } + + // utility functions + function debug($message, $debug_type = N_DEBUGTYPE_INFO, $log_level = PEAR_LOG_DEBUG, $ident=false) { + if (!$ident) { + $ident = (isset($this) && is_a($this, __CLASS__))?get_class($this):__CLASS__; + } + NDebug::debug($message, $debug_type, $log_level, $ident); + } +} +?> diff --git a/lib/view/helpers/action_track_helper.php b/lib/view/helpers/action_track_helper.php new file mode 100644 index 0000000..61e5a58 --- /dev/null +++ b/lib/view/helpers/action_track_helper.php @@ -0,0 +1,63 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1.14 + * @todo If more than 60 seconds, turn to minutes. + */ +class ActionTrackHelper { + function assetEditStatus($params) { + $action_track = NModel::factory('action_track'); + $url = $action_track->cleanUrl($_SERVER['REQUEST_URI']); + $action_track->setAssetValues($url); + $status = $action_track->getActiveEdits(); + $this->displayEditStatus($status); + } + + function displayEditStatus($items) { + $time_now = time(); + foreach ($items as $item) { + $time_diff = $time_now - $item['timestamp']; + + // Grab information about the person editing. + $user_info = NModel::factory('cms_auth'); + $user_info->id = $item['user_id']; + if ($user_info->find()) { + while ($user_info->fetch()) { + $name = $user_info->real_name; + $email = $user_info->email; + } + } + unset($cms_auth); + + // Check to see if you're the one editing. + $auth = new NAuth(); + $current_user_id = $auth->currentUserID(); + unset($auth); + + // Output the item. + if ($current_user_id == $item['user_id']) { + print '
      You have been editing this record for ' . $time_diff . ' seconds.
      '; + } else { + print '
      ' . $name . ' was editing this item ' . $time_diff . ' seconds ago.
      '; + } + } + } +} +?> diff --git a/lib/view/helpers/asset_tag_helper.php b/lib/view/helpers/asset_tag_helper.php new file mode 100755 index 0000000..65a8db2 --- /dev/null +++ b/lib/view/helpers/asset_tag_helper.php @@ -0,0 +1,370 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class AssetTagHelper extends TagHelper { + /* + function autoDiscoveryLinkTag($link='', $type='rss', $options=array(), $tag_options=array()) { + $t_options = array(); + $t_options['rel'] = isset($tag_options['rel'])?$tag_options['rel']:'alternate'; + $t_options['type'] = 'application/' . (isset($tag_options['type'])?:'') . '+xml'; + $t_options['title'] = isset($tag_options['title'])?$tag_options['rel']:'alternate'; + $t_options['href'] = ; + return TagHelper::tag($link, $t_options); + } + */ + + function imageTag($src, $alt='', $options=array()) { + if (is_file($_SERVER['DOCUMENT_ROOT'] . $src)) { + $file = $_SERVER['DOCUMENT_ROOT'] . $src; + } else if (is_file(BASE_DIR . $src)) { + $file = BASE_DIR . $src; + } else { + return ''; + } + if (is_string($options)) { + require_once 'vendor/spyc.php'; + $val = @Spyc::YAMLLoad($options); + if (!empty($val)) { // it's a YAML array, so load it into options['update'] + $options = $val; + } + } + $code = ''; + if (is_array($options)) { + $options['style'] = 'border:0;' . (isset($options['style'])?$options['style']:''); + foreach ($options as $key=>$val) { + $code .= " $key=\"$val\""; + } + } else { + $code = ' style="border:0;" ' . $options; + } + $size = getImageSize($file); + $src = (defined('EXTERNAL_CACHE') && constant('EXTERNAL_CACHE')?EXTERNAL_CACHE:'') . $src; + return '' . $alt . ''; + } + function imageTagFunc($params, &$view) { + if (!isset($params['src'])) return; + $src = $params['src']; + unset($params['src']); + $alt = ''; + if (isset($params['alt'])) { + $alt = $params['alt']; + unset($params['alt']); + } + return AssetTagHelper::imageTag($src, $alt, $params); + } + + function lastModifiedIncludeTime($filename) { + if (file_exists($filename)) { + $timestamp = filemtime($filename); + } + return $timestamp; + } + + function stylesheetLinkTagFunc($params, &$view) { + if (!isset($params['href'])) return; + $href = $params['href']; + $link_string = ''; + + // Setup some defaults + $rel = isset($params['rel'])?$params['rel']:'stylesheet'; + $rev = isset($params['rev'])?$params['rev']:''; + $type = isset($params['type'])?$params['type']:'text/css'; + $media = isset($params['media'])?$params['media']:'screen'; + $title = isset($params['title'])?$params['title']:''; + + // Did we get a comma delimited string? If so make an array. + if(stristr($href,',')){$href = explode(',',$href);} + if(stristr($href,', ')){$href = explode(', ',$href);} + + // Did we get an array of files + if(is_array($href)){ + + $link_string = ''; + foreach ($href as $sheet) { + $link_string .= '\n"; + } + + }else{ + // Do the normal stylesheet parsing + $href .= preg_match('/\.css/', $href)?'':'.css'; + if (file_exists(DOCUMENT_ROOT. '/includes/' . $href)) { + $href = '/includes/' . $href; + $full_path = DOCUMENT_ROOT . $href; + } else if (file_exists(DOCUMENT_ROOT. '/stylesheets/' . $href)) { + $href = '/stylesheets/' . $href; + $full_path = DOCUMENT_ROOT . $href; + } else if (file_exists(BASE_DIR . '/nterchange/stylesheets/' . $href)) { + $href = '/nterchange/stylesheets/' . $href; + $full_path = BASE_DIR . $href; + } + $timestamp = AssetTagHelper::lastModifiedIncludeTime($full_path); + if (isset($timestamp) && $timestamp > 0) $href .= '?' . $timestamp; + + $link_string .= ''; + } + + return $link_string; + + } + + + + function javascriptIncludeTagFunc($params, &$view) { + if (!isset($params['src'])) return; + $src = $params['src']; + + // Set some default options + $language = isset($params['language'])?$params['language']:''; + $charset = isset($params['charset'])?$params['charset']:''; + $type = isset($params['type'])?$params['type']:'text/javascript'; + $timestamp = AssetTagHelper::lastModifiedIncludeTime($full_path); + if (isset($timestamp) && $timestamp > 0) $src .= '?' . $timestamp; + + // Did we get a comma delimited string? If so make an array. + if(stristr($src,',')){$src = explode(',',$src);} + + // Did we get an array of files + if(is_array($src)){ + // Are we in production, if so combine and compress the files + if(defined('ENVIRONMENT') && ENVIRONMENT == 'production'){ + $src = implode(".js,",$src); + $include_string = ''; + }else{ + $include_string = ''; + foreach ($src as $script) { + $include_string .= '\n"; + } + } + }else{ + // Do the normal JS parsing + $src .= preg_match('/\.js/', $src)?'':'.js'; + if (file_exists(DOCUMENT_ROOT. '/includes/' . $src)) { + $src = '/includes/' . $src; + $full_path = DOCUMENT_ROOT . $src; + } else if (file_exists(DOCUMENT_ROOT. '/javascripts/' . $src)) { + $src = '/javascripts/' . $src; + $full_path = DOCUMENT_ROOT . $src; + } else if (file_exists(BASE_DIR . '/nterchange/javascripts/' . $src)) { + $src = '/nterchange/javascripts/' . $src; + $full_path = BASE_DIR . $src; + } else { + return; + } + $include_string = ''; + } + return $include_string; + } + + function imageSize($src, $options = array()) { + $phpfile = preg_match('/\.php/', $src); + $file = $phpfile?preg_replace('/\.php\?.*$/', '.php', $src):$src; + if (is_file($_SERVER['DOCUMENT_ROOT'] . $file)) { + $file = $_SERVER['DOCUMENT_ROOT'] . $file; + } else if (is_file(BASE_DIR . $file)) { + $file = BASE_DIR . $file; + } else { + return ''; + } + + if (!$phpfile) { + $size = @getImageSize($file); + } else { + // normalize the query string in the src (if any) + if (preg_match('/\?(.*)$/', $src, $matches) && isset($matches[1]) && $matches[1]) { + if (preg_match_all('/([^=]+)=([^\&$]+)/', $matches[1], $submatches)) { + $qs = ''; + foreach ($submatches[1] as $i=>$key) { + $key = preg_replace('/^\&/', '', $key); + $val = $submatches[2][$i]; + $qs .= ($qs?'&':'') . $key . '=' . urlencode($val); + } + $src = preg_replace('/\?[^$]+/', '?' . $qs, $src); + } + } + $size = AssetTagHelper::otfImageSize($src); + } + return ' width="' . $size[0] . '" height="' . $size[1] . '"'; + } + + function imageWidth($src, $options = array()) { + $phpfile = preg_match('/\.php/', $src); + $file = $phpfile?preg_replace('/\.php\?.*$/', '.php', $src):$src; + if (is_file($_SERVER['DOCUMENT_ROOT'] . $file)) { + $file = $_SERVER['DOCUMENT_ROOT'] . $file; + } else if (is_file(BASE_DIR . $file)) { + $file = BASE_DIR . $file; + } else { + return ''; + } + + if (!$phpfile) { + $size = @getImageSize($file); + } else { + // normalize the query string in the src (if any) + if (preg_match('/\?(.*)$/', $src, $matches) && isset($matches[1]) && $matches[1]) { + if (preg_match_all('/([^=]+)=([^\&$]+)/', $matches[1], $submatches)) { + $qs = ''; + foreach ($submatches[1] as $i=>$key) { + $key = preg_replace('/^\&/', '', $key); + $val = $submatches[2][$i]; + $qs .= ($qs?'&':'') . $key . '=' . urlencode($val); + } + $src = preg_replace('/\?[^$]+/', '?' . $qs, $src); + } + } + $size = AssetTagHelper::otfImageSize($src); + } + return $size[0]; + } + + function imageHeight($src, $options = array()) { + $phpfile = preg_match('/\.php/', $src); + $file = $phpfile?preg_replace('/\.php\?.*$/', '.php', $src):$src; + if (is_file($_SERVER['DOCUMENT_ROOT'] . $file)) { + $file = $_SERVER['DOCUMENT_ROOT'] . $file; + } else if (is_file(BASE_DIR . $file)) { + $file = BASE_DIR . $file; + } else { + return ''; + } + + if (!$phpfile) { + $size = @getImageSize($file); + } else { + // normalize the query string in the src (if any) + if (preg_match('/\?(.*)$/', $src, $matches) && isset($matches[1]) && $matches[1]) { + if (preg_match_all('/([^=]+)=([^\&$]+)/', $matches[1], $submatches)) { + $qs = ''; + foreach ($submatches[1] as $i=>$key) { + $key = preg_replace('/^\&/', '', $key); + $val = $submatches[2][$i]; + $qs .= ($qs?'&':'') . $key . '=' . urlencode($val); + } + $src = preg_replace('/\?[^$]+/', '?' . $qs, $src); + } + } + $size = AssetTagHelper::otfImageSize($src); + } + return $size[1]; + } + + function cssImageSize($src, $options = array()) { + $phpfile = preg_match('/\.php/', $src); + $file = $phpfile?preg_replace('/\.php\?.*$/', '.php', $src):$src; + if (is_file($_SERVER['DOCUMENT_ROOT'] . $file)) { + $file = $_SERVER['DOCUMENT_ROOT'] . $file; + } else if (is_file(BASE_DIR . $file)) { + $file = BASE_DIR . $file; + } else { + return ''; + } + + if (!$phpfile) { + $size = @getImageSize($file); + } else { + // normalize the query string in the src (if any) + if (preg_match('/\?(.*)$/', $src, $matches) && isset($matches[1]) && $matches[1]) { + if (preg_match_all('/([^=]+)=([^\&$]+)/', $matches[1], $submatches)) { + $qs = ''; + foreach ($submatches[1] as $i=>$key) { + $key = preg_replace('/^\&/', '', $key); + $val = $submatches[2][$i]; + $qs .= ($qs?'&':'') . $key . '=' . urlencode($val); + } + $src = preg_replace('/\?[^$]+/', '?' . $qs, $src); + } + } + $size = AssetTagHelper::otfImageSize($src); + } + $img_width = $size[0]; + $img_height = $size[1]; + if($options['pad_width'] && isset($options['pad_width'])){ + $img_width += $options['pad_width']; + } + if($options['pad_height'] && isset($options['pad_height'])){ + $img_height += $options['pad_height']; + } + return ' style="width:' . $img_width . 'px;height:' . $img_height . 'px;"'; + } + + function otfImageSize($src, $i=0) { + // grab the file, write a file, get the size + include_once 'HTTP/Request.php'; + $req = new HTTP_Request(preg_replace('/\/$/', '', PUBLIC_SITE) . $src); + $size = false; + if (!PEAR::isError($req->sendRequest())) { + $tmpnam = tempnam(CACHE_DIR . '/ntercache', 'otfimage'); + $fp = fopen($tmpnam, 'w'); + fwrite($fp, $req->getResponseBody()); + fclose($fp); + $size = @getImageSize($tmpnam); + unlink($tmpnam); + } + if (!$size && $i<3) { + $size = AssetTagHelper::otfImageSize($src, $i+1); + } + return $size; + } + + function imageSizeFunc($params, &$view) { + if (!isset($params['src'])) return; + $src = $params['src']; + unset($params['src']); + return AssetTagHelper::imageSize($src, $params); + } + + function imageWidthFunc($params, &$view) { + if (!isset($params['src'])) return; + $src = $params['src']; + unset($params['src']); + return AssetTagHelper::imageWidth($src, $params); + } + + function imageHeightFunc($params, &$view) { + if (!isset($params['src'])) return; + $src = $params['src']; + unset($params['src']); + return AssetTagHelper::imageHeight($src, $params); + } + + + function cssImageSizeFunc($params, &$view) { + if (!isset($params['src'])) return; + $src = $params['src']; + unset($params['src']); + return AssetTagHelper::cssImageSize($src, $params); + } + + function persistentUrl($params) { + if (defined('PERSISTENT_UPLOAD_URLS') && PERSISTENT_UPLOAD_URLS) { + $url_path = NDownload::cleanUrl($params['file']); + $parts = NDownload::getAssetAttributes($url_path); + NDownload::setAssetAttributes($parts); + $model_name = NDownload::getAssetModelName(); + $url = UPLOAD_DIR . '/' . $model_name . '/' . $params['field'] . '/' . $params['id']; + return $url; + } else { + return $params['file']; + } + } +} +?> diff --git a/lib/view/helpers/date_helper.php b/lib/view/helpers/date_helper.php new file mode 100644 index 0000000..cbfe0d3 --- /dev/null +++ b/lib/view/helpers/date_helper.php @@ -0,0 +1,192 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class DateHelper { + /** + * Options in different languages + * + * @access private + * @var array + */ + var $_locale = array( + 'en' => array ( + 'weekdays_short'=> array ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'), + 'weekdays_long' => array ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + 'months_long' => array ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') + ), + 'de' => array ( + 'weekdays_short'=> array ('So', 'Mon', 'Di', 'Mi', 'Do', 'Fr', 'Sa'), + 'weekdays_long' => array ('Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'), + 'months_short' => array ('Jan', 'Feb', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dez'), + 'months_long' => array ('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember') + ), + 'fr' => array ( + 'weekdays_short'=> array ('Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'), + 'weekdays_long' => array ('Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'), + 'months_short' => array ('Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc'), + 'months_long' => array ('Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre') + ), + 'hu' => array ( + 'weekdays_short'=> array ('V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'), + 'weekdays_long' => array ('vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'), + 'months_short' => array ('jan', 'feb', 'márc', 'ápr', 'máj', 'jún', 'júl', 'aug', 'szept', 'okt', 'nov', 'dec'), + 'months_long' => array ('január', 'február', 'március', 'április', 'május', 'június', 'július', 'augusztus', 'szeptember', 'október', 'november', 'december') + ), + 'pl' => array ( + 'weekdays_short'=> array ('Nie', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'), + 'weekdays_long' => array ('Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'), + 'months_short' => array ('Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'), + 'months_long' => array ('Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień') + ), + 'sl' => array ( + 'weekdays_short'=> array ('Ned', 'Pon', 'Tor', 'Sre', 'Cet', 'Pet', 'Sob'), + 'weekdays_long' => array ('Nedelja', 'Ponedeljek', 'Torek', 'Sreda', 'Cetrtek', 'Petek', 'Sobota'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij', 'Avgust', 'September', 'Oktober', 'November', 'December') + ), + 'ru' => array ( + 'weekdays_short'=> array ('Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'), + 'weekdays_long' => array ('Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'), + 'months_short' => array ('Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'), + 'months_long' => array ('Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь') + ), + 'es' => array ( + 'weekdays_short'=> array ('Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'), + 'weekdays_long' => array ('Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'), + 'months_short' => array ('Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'), + 'months_long' => array ('Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre') + ), + 'da' => array ( + 'weekdays_short'=> array ('Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'), + 'weekdays_long' => array ('Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December') + ), + 'is' => array ( + 'weekdays_short'=> array ('Sun', 'Mán', 'Þri', 'Mið', 'Fim', 'Fös', 'Lau'), + 'weekdays_long' => array ('Sunnudagur', 'Mánudagur', 'Þriðjudagur', 'Miðvikudagur', 'Fimmtudagur', 'Föstudagur', 'Laugardagur'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maí', 'Jún', 'Júl', 'Ágú', 'Sep', 'Okt', 'Nóv', 'Des'), + 'months_long' => array ('Janúar', 'Febrúar', 'Mars', 'Apríl', 'Maí', 'Júní', 'Júlí', 'Ágúst', 'September', 'Október', 'Nóvember', 'Desember') + ), + 'it' => array ( + 'weekdays_short'=> array ('Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'), + 'weekdays_long' => array ('Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'), + 'months_short' => array ('Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'), + 'months_long' => array ('Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre') + ), + 'sk' => array ( + 'weekdays_short'=> array ('Ned', 'Pon', 'Uto', 'Str', 'Štv', 'Pia', 'Sob'), + 'weekdays_long' => array ('Nedeža', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Máj', 'Jún', 'Júl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December') + ), + 'cs' => array ( + 'weekdays_short'=> array ('Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So'), + 'weekdays_long' => array ('Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'), + 'months_short' => array ('Led', 'Úno', 'Bře', 'Dub', 'Kvě', 'Čen', 'Čec', 'Srp', 'Zář', 'Říj', 'Lis', 'Pro'), + 'months_long' => array ('Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec') + ), + 'hy' => array ( + 'weekdays_short'=> array ('Կրկ', 'Երկ', 'Երք', 'Չրք', 'Հնգ', 'Ուր', 'Շբթ'), + 'weekdays_long' => array ('Կիրակի', 'Երկուշաբթի', 'Երեքշաբթի', 'Չորեքշաբթի', 'Հինգշաբթի', 'Ուրբաթ', 'Շաբաթ'), + 'months_short' => array ('Հնվ', 'Փտր', 'Մրտ', 'Ապր', 'Մյս', 'Հնս', 'Հլս', 'Օգս', 'Սպտ', 'Հկտ', 'Նյմ', 'Դկտ'), + 'months_long' => array ('Հունվար', 'Փետրվար', 'Մարտ', 'Ապրիլ', 'Մայիս', 'Հունիս', 'Հուլիս', 'Օգոստոս', 'Սեպտեմբեր', 'Հոկտեմբեր', 'Նոյեմբեր', 'Դեկտեմբեր') + ), + 'nl' => array ( + 'weekdays_short'=> array ('Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za'), + 'weekdays_long' => array ('Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December') + ), + 'et' => array ( + 'weekdays_short'=> array ('P', 'E', 'T', 'K', 'N', 'R', 'L'), + 'weekdays_long' => array ('Pühapäev', 'Esmaspäev', 'Teisipäev', 'Kolmapäev', 'Neljapäev', 'Reede', 'Laupäev'), + 'months_short' => array ('Jaan', 'Veebr', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dets'), + 'months_long' => array ('Jaanuar', 'Veebruar', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'August', 'September', 'Oktoober', 'November', 'Detsember') + ), + 'tr' => array ( + 'weekdays_short'=> array ('Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cts'), + 'weekdays_long' => array ('Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'), + 'months_short' => array ('Ock', 'Şbt', 'Mrt', 'Nsn', 'Mys', 'Hzrn', 'Tmmz', 'Ağst', 'Eyl', 'Ekm', 'Ksm', 'Arlk'), + 'months_long' => array ('Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık') + ), + 'no' => array ( + 'weekdays_short'=> array ('Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'), + 'weekdays_long' => array ('Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'), + 'months_long' => array ('Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember') + ), + 'eo' => array ( + 'weekdays_short'=> array ('Dim', 'Lun', 'Mar', 'Mer', 'Ĵaŭ', 'Ven', 'Sab'), + 'weekdays_long' => array ('Dimanĉo', 'Lundo', 'Mardo', 'Merkredo', 'Ĵaŭdo', 'Vendredo', 'Sabato'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aŭg', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januaro', 'Februaro', 'Marto', 'Aprilo', 'Majo', 'Junio', 'Julio', 'Aŭgusto', 'Septembro', 'Oktobro', 'Novembro', 'Decembro') + ), + 'ua' => array ( + 'weekdays_short'=> array('Ндл', 'Пнд', 'Втр', 'Срд', 'Чтв', 'Птн', 'Сбт'), + 'weekdays_long' => array('Неділя', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'ятниця', 'Субота'), + 'months_short' => array('Січ', 'Лют', 'Бер', 'Кві', 'Тра', 'Чер', 'Лип', 'Сер', 'Вер', 'Жов', 'Лис', 'Гру'), + 'months_long' => array('Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень') + ), + 'ro' => array ( + 'weekdays_short'=> array ('Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sam'), + 'weekdays_long' => array ('Duminica', 'Luni', 'Marti', 'Miercuri', 'Joi', 'Vineri', 'Sambata'), + 'months_short' => array ('Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + 'months_long' => array ('Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie') + ), + 'he' => array ( + 'weekdays_short'=> array ('ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'), + 'weekdays_long' => array ('יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'שבת'), + 'months_short' => array ('ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'), + 'months_long' => array ('ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר') + ), + 'sv' => array ( + 'weekdays_short'=> array ('Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör'), + 'weekdays_long' => array ('Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag'), + 'months_short' => array ('Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'), + 'months_long' => array ('Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December') + ), + 'pt' => array ( + 'weekdays_short'=> array ('Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'), + 'weekdays_long' => array ('Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'), + 'months_short' => array ('Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'), + 'months_long' => array ('Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro') + ) + ); + + function dateFormatLocal($date, $format='%B %d, %Y', $language='en') { + if (!is_numeric($date)) { + $date = strtotime($date); + } + $class_vars = get_class_vars(__CLASS__); + $language = isset($class_vars['_locale'][$language])?$language:'en'; + $locale = $class_vars['_locale'][$language]; + + $day_of_week = strftime('%w', $date); + $month_of_year = strftime('%m', $date) - 1; // start at 0-index + $finds = array(strftime('%A', $date), strftime('%a', $date), strftime('%B', $date), strftime('%b', $date)); + $replaces = array($locale['weekdays_long'][$day_of_week], $locale['weekdays_short'][$day_of_week], $locale['months_long'][$month_of_year], $locale['months_short'][$month_of_year]); + + $date = str_replace($finds, $replaces, strftime($format, $date)); + return $date; + } +} +?> \ No newline at end of file diff --git a/lib/view/helpers/filesystem_helper.php b/lib/view/helpers/filesystem_helper.php new file mode 100644 index 0000000..6789068 --- /dev/null +++ b/lib/view/helpers/filesystem_helper.php @@ -0,0 +1,54 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class FilesystemHelper { + function filesizeFormat($file_path) { + if ($file_path = FilesystemHelper::_findFilePath($file_path)) { + return NFilesystem::filesize_format(filesize($file_path)); + } + return false; + } + + function downloadTime($file_path) { + if ($file_path = FilesystemHelper::_findFilePath($file_path)) { + return NFilesystem::download_time(filesize($file_path)); + } + return false; + } + + function _findFilePath($file_path) { + if (false == ($test_path = realpath($file_path))) { + $file_path = realpath(NServer::env('DOCUMENT_ROOT') . $file_path); + } + return $file_path; + } + + /* + * Helper to grab a download icon out of /images/icons if it exists. + * + * Returns an image tag. + */ + function downloadIcon($file_path) { + return NFilesystem::download_icon($file_path); + } +} +?> diff --git a/lib/view/helpers/javascript_helper.php b/lib/view/helpers/javascript_helper.php new file mode 100755 index 0000000..7e6c1f0 --- /dev/null +++ b/lib/view/helpers/javascript_helper.php @@ -0,0 +1,152 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class JavascriptHelper extends TagHelper { + var $callbacks = array('uninitialized', 'loading', 'loaded', 'interactive', 'success', 'failure', 'complete'); + var $ajax_options = array('before', 'after', 'condition', 'confirm', 'url', 'asynchronous', 'method', 'insertion', 'position', 'form', 'with', 'update', 'script'); + var $javascript_path = '/javascripts'; + + function linkToFunction(&$controller, $name, $function, $html_options=array()) { + $options = array_merge(array('href'=>'#', 'onclick'=>"$function;return false;"), $html_options); + return TagHelper::contentTag('a', $name, $options); + } + + function linkToFunctionFunc($params, &$view) { + $controller = &$view->controller; + $name = isset($params['name'])?$params['name']:''; + if (isset($params['name'])) unset($params['name']); + $function = isset($params['function'])?$params['function']:''; + if (isset($params['function'])) unset($params['function']); + return JavascriptHelper::linkToFunction($controller, $name, $function, $params); + } + + function linkToRemote(&$controller, $name, $options = array(), $html_options = array()) { + return JavascriptHelper::linkToFunction($controller, $name, JavascriptHelper::remoteFunction($controller, $options), $html_options); + } + + function linkToRemoteFunc($params, &$view) { + $controller = &$view->controller; + $name = isset($params['name'])?$params['name']:''; + if (isset($params['name'])) unset($params['name']); + $options = array(); + $class_props = get_class_vars(__CLASS__); + foreach ($class_props['callbacks'] as $callback) { + if (isset($params[$callback])) { + $options[$callback] = $params[$callback]; + unset($params[$callback]); + } + } + foreach ($class_props['ajax_options'] as $ajax_option) { + if (isset($params[$ajax_option])) { + $options[$ajax_option] = $params[$ajax_option]; + unset($params[$ajax_option]); + } + } + + return JavascriptHelper::linkToRemote($controller, $name, $options, $params); + } + + function remoteFunction(&$controller, $options) { + $javascript_options = JavascriptHelper::optionsForAjax($options); + + $update = ''; + if (isset($options['update']) && is_string($options['update'])) { + require_once 'vendor/spyc.php'; + $val = @Spyc::YAMLLoad($options['update']); + if (!empty($val)) { // it's a YAML array, so load it into options['update'] + $options['update'] = $val; + } + } + if (isset($options['update']) && is_array($options['update'])) { + $update = array(); + if (isset($options['update']['success'])) { + $update[] = "success:'{$options['update']['success']}'"; + } + if (isset($options['update']['failure'])) { + $update[] = "failure:'{$options['update']['failure']}'"; + } + $update = implode(',', $update); + } else if (isset($options['update'])) { + $update = $options['update']; + } + + $function = isset($options['update'])?"new Ajax.Updater('$update', ":'new Ajax.Request('; + $function .= "'" . UrlHelper::urlFor($controller, $options['url']) . "'"; + $function .= ', ' . $javascript_options . ')'; + + $function = (isset($options['before'])?"{$options['before']}; ":'') . $function; + $function .= (isset($options['after'])?"; {$options['after']};":''); + $function = isset($options['condition'])?'if(' . $options['condition'] . '){' . $function . '}':$function; + $function = isset($options['confirm'])?'if(confirm' . $options['condition'] . '){' . $function . '}':$function; + + return $function; + } + + function optionsforJavascript($options) { + $js_options = ''; + foreach ($options as $k=>$v) { + if (is_bool($v)) { + $v = $v?'true':'false'; + } + $js_options[] = $k . ':' . (string)$v; + } + return '{' . implode(', ', $js_options) . '}'; + } + + function optionsForAjax($options) { + $js_options = JavascriptHelper::buildCallbacks($options); + $js_options['asynchronous'] = isset($options['type']) && $options['type'] != 'synchronous'?false:true; + if (isset($options['method'])) $js_options['method'] = JavascriptHelper::methodOptionToS($options['method']); + if (isset($options['position'])) $js_options['insertion'] = "Insertion.{$options['position']}"; + if (isset($options['script'])) $js_options['evalScripts'] = $options['script']?'true':'false'; + + if (isset($options['form'])) { + $js_options['parameters'] = 'Form.serialize(this)'; + } else if (isset($options['with'])) { + $js_options['parameters'] = $options['with']; + } + + return JavascriptHelper::optionsForJavascript($js_options); + } + + function buildCallbacks($options) { + $callbacks = array(); + $class_props = get_class_vars(__CLASS__); + $default_callbacks = $class_props['callbacks']; + foreach ($options as $callback=>$code) { + if (in_array($callback, $default_callbacks)) { + $name = 'on' . ucfirst($callback); + $callbacks[$name] = 'function(request){' . $code . '}'; + } + } + return $callbacks; + } + + function escapeJavascript($js) { + return preg_replace(array('/\r\n|\n|\r/', '/([\"\'])/'), array('\\n', "\\$1"), $js); + } +} +/* +hi +
      +*/ +?> \ No newline at end of file diff --git a/lib/view/helpers/search_helper.php b/lib/view/helpers/search_helper.php new file mode 100644 index 0000000..715108c --- /dev/null +++ b/lib/view/helpers/search_helper.php @@ -0,0 +1,59 @@ + + * @copyright 2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.1 + */ +class SearchHelper extends TagHelper { + var $admin_only_fields = array('id', 'cms_active', 'cms_deleted', 'cms_draft', 'cms_created', 'cms_modified', 'cms_modified_by_user'); + + function searchFieldListSelect($params) { + $asset = $params['asset']?$params['asset']:null; + $searched_field = $params['searched_field']?$params['searched_field']:null; + if (isset($searched_field)) $searched_field = str_replace(" ", "_", strtolower($searched_field)); + $model = NModel::factory($asset); + $fields = $model->fields(); + // Remove a bunch of fields if you're not an admin - makes it a little bit simpler. + $auth = new NAuth(); + $current_user_level = $auth->getAuthData('user_level'); + unset($auth); + // Preload for the search_field default. + $acon = NController::factory('asset'); + $select = 'Search Field: '; + unset($model); + unset($acon); + print $select; + } +} +?> diff --git a/lib/view/helpers/tag_helper.php b/lib/view/helpers/tag_helper.php new file mode 100755 index 0000000..cd1ea4a --- /dev/null +++ b/lib/view/helpers/tag_helper.php @@ -0,0 +1,71 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class TagHelper { + function tag($name, $options=array(), $open=false) { + return "<$name" . TagHelper::_tag_options($options) . ($open?'>':' />'); + } + + function contentTag($name, $content, $options=array()) { + return "<$name" . TagHelper::_tag_options($options) . '>' . htmlspecialchars($content, ENT_QUOTES) . ""; + } + + function _tag_options($options) { + $cleaned_options = array(); + foreach ($options as $k=>$v) { + if (!empty($v)) + $cleaned_options[$k] = $v; + } + $str = ''; + foreach ($cleaned_options as $k=>$v) { + $str .= " $k=\"$v\""; + } + return $str; + } + + function closeTags($string) { + // match opened tags + if(preg_match_all('/<([a-z\:\-]+)[ >]/', $string, $start_tags)) { + $start_tags = $start_tags[1]; + // match closed tags + if(preg_match_all('/<\/([a-z]+)>/', $string, $end_tags)) { + $complete_tags = array(); + $end_tags = $end_tags[1]; + foreach($start_tags as $key => $val) { + $posb = array_search($val, $end_tags); + if(is_integer($posb)) { + unset($end_tags[$posb]); + } else { + $complete_tags[] = $val; + } + } + } else { + $complete_tags = $start_tags; + } + $complete_tags = array_reverse($complete_tags); + for($i = 0; $i < count($complete_tags); $i++) { + $string .= ''; + } + } + return $string; + } +} +?> \ No newline at end of file diff --git a/lib/view/helpers/url_helper.php b/lib/view/helpers/url_helper.php new file mode 100755 index 0000000..9a85c17 --- /dev/null +++ b/lib/view/helpers/url_helper.php @@ -0,0 +1,162 @@ + + * @copyright 2005-2007 nonfiction studios inc. + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version SVN: $Id$ + * @link http://www.nterchange.com/ + * @since File available since Release 3.0 + */ +class urlHelper extends TagHelper { + function urlFor(&$controller, $params) { + if (is_a($controller, 'NView')) { + $controller = &$controller->controller; + } + if (is_string($params)) { + $href = $params; + // load controller, action & id parameters + if (preg_match('|(?:(controller):([^;]+);)?(?:(action):([^;]+);)?(?:(id):([^;]+);)?|', $href, $matches)) { + $params = array(); + for ($i=1;$ibase_dir)?'/' . $controller->base_dir:''; + if (isset($params['controller'])) { + $href .= '/' . $params['controller']; + unset($params['controller']); + } else if (is_object($controller)) { + $href .= '/' . $controller->name; + } + if (isset($params['action'])) { + $href .= '/' . $params['action']; + if (isset($params['id'])) { + $href .= '/' . $params['id']; + unset($params['id']); + } + unset($params['action']); + } + $referer = false; + if (isset($params['referer']) && $params['referer']) { + $referer = urlencode($_SERVER['REQUEST_URI']); + unset($params['referer']); + } + if (!empty($params)) { + foreach ($params as $key=>$val) { + if (!$val) continue; + $href .= (preg_match('|\?|', $href)?'&':'?') . $key . '=' . (is_string($val)?urlencode($val):$val); + } + } + if ($referer) { + $href .= (preg_match('|\?|', $href)?'&':'?') . '_referer=' . $referer; + } + return $href; + } + + function urlForFunc($params, &$view) { + $url = $this->urlFor($view->controller, $params); + return $url; + } + + function linkTo(&$controller, $text, $href, $html_options = null) { + $href = urlHelper::urlFor($controller, $href); + if (!$href) return $text?$text:''; + if (isset($html_options['confirm'])) { + $confirm = $html_options['confirm']; + $confirm = htmlspecialchars($confirm, ENT_NOQUOTES); + $confirm = str_replace("'", "\'", $confirm); + $confirm = str_replace('"', '"', $confirm); + $html_options['onclick'] = "return confirm('{$confirm}');"; + unset($html_options['confirm']); + } + if (isset($html_options['referer']) && $html_options['referer']) { + $href .= (preg_match('|\?|', $href)?'&':'?') . '_referer=' . urlencode($_SERVER['REQUEST_URI']); + } + $html_options['href'] = $href; + return TagHelper::contentTag('a', $text, $html_options); + } + + function linkToFunc($params, &$view) { + if (!isset($params['href']) || !$params['href']) return isset($params['text'])?$params['text']:''; + $href = $params['href']; + unset($params['href']); + $text = $params['text']; + unset($params['text']); + return $this->linkTo($view->controller, $text, $href, $params); + } + + function convertInternalLink($tpl_output, &$view) { + if ($view->controller->name != 'page') { + return $tpl_output; + } + $controller = &$view->controller; + // fix all links + preg_match_all("/(href|action)=(\\\\)?[\"\']([\/]?_page([^\"\'\?\#]+)([\"\'\?])?)/i", $tpl_output, $matches); + $page_model = &$controller->getDefaultModel(); + $urls = array(); + for ($x=0;$xnterchange == true) { + $path = '/' . ADMIN_SITE . $controller->name . '/' . ($controller->edit == false?'preview/':'surftoedit/') . $page_id; + if ($matches[5][$x] == '?') $path .= '?'; + } else { + $path = $page_model->IdToURL($page_id, false, false); + if ($matches[5][$x] == '?') $path .= '?'; + } + if ($matches[5][$x] == '"') $path .= '"'; + else if ($matches[5][$x] == '\'') $path .= '\''; + if ($path) $urls[] = $path; + } + for ($x=0;$xnterchange == true) { + $path = '/' . ADMIN_SITE . $controller->name . '/' . ($controller->edit == false?'preview/':'surftoedit/') . $page_id; + if ($matches[3][$x] == '?') $path .= '&'; + } else { + $path = $page_model->IdToURL($page_id, false, false); + if ($matches[3][$x] == '?') $path .= '?'; + } + if ($path) $urls[] = $path; + } + for ($x=0;$x \ No newline at end of file diff --git a/nterchange/check.php b/nterchange/check.php new file mode 100644 index 0000000..084afe6 --- /dev/null +++ b/nterchange/check.php @@ -0,0 +1,52 @@ + + + + + + <?= $title ?> + + + +

      + + + + + + + + + + + + + + + + + + + + + + + + find(); + ?> + + +
      ENVIRONMENT
      DOCUMENT_ROOT
      DEFAULT_PAGE_EXTENSION
      GZIP_COMPRESSION
      CONF_DIR
      ROOT_DIR
      CACHE_DIR
      APP_NAME
      APP_DIR
      BASE_DIR
      Database
      # of users
      + + + diff --git a/nterchange/images/add.gif b/nterchange/images/add.gif new file mode 100644 index 0000000..ddfa493 Binary files /dev/null and b/nterchange/images/add.gif differ diff --git a/nterchange/images/created_by.gif b/nterchange/images/created_by.gif new file mode 100644 index 0000000..76989e3 Binary files /dev/null and b/nterchange/images/created_by.gif differ diff --git a/nterchange/images/drag.gif b/nterchange/images/drag.gif new file mode 100644 index 0000000..ad3b6e6 Binary files /dev/null and b/nterchange/images/drag.gif differ diff --git a/nterchange/images/edit.gif b/nterchange/images/edit.gif new file mode 100644 index 0000000..bff4def Binary files /dev/null and b/nterchange/images/edit.gif differ diff --git a/nterchange/images/icon_alert.png b/nterchange/images/icon_alert.png new file mode 100644 index 0000000..73bb862 Binary files /dev/null and b/nterchange/images/icon_alert.png differ diff --git a/nterchange/images/icon_check.gif b/nterchange/images/icon_check.gif new file mode 100644 index 0000000..359469b Binary files /dev/null and b/nterchange/images/icon_check.gif differ diff --git a/nterchange/images/icon_error.png b/nterchange/images/icon_error.png new file mode 100644 index 0000000..a2949e5 Binary files /dev/null and b/nterchange/images/icon_error.png differ diff --git a/nterchange/images/icon_help.png b/nterchange/images/icon_help.png new file mode 100644 index 0000000..53b3187 Binary files /dev/null and b/nterchange/images/icon_help.png differ diff --git a/nterchange/images/icon_x.gif b/nterchange/images/icon_x.gif new file mode 100644 index 0000000..6cf3ab0 Binary files /dev/null and b/nterchange/images/icon_x.gif differ diff --git a/nterchange/images/indicator.gif b/nterchange/images/indicator.gif new file mode 100644 index 0000000..085ccae Binary files /dev/null and b/nterchange/images/indicator.gif differ diff --git a/nterchange/images/listarrow.gif b/nterchange/images/listarrow.gif new file mode 100644 index 0000000..01f428b Binary files /dev/null and b/nterchange/images/listarrow.gif differ diff --git a/nterchange/images/nterchange.gif b/nterchange/images/nterchange.gif new file mode 100644 index 0000000..91358f7 Binary files /dev/null and b/nterchange/images/nterchange.gif differ diff --git a/nterchange/images/nterchange3_manual.pdf b/nterchange/images/nterchange3_manual.pdf new file mode 100644 index 0000000..7c38554 Binary files /dev/null and b/nterchange/images/nterchange3_manual.pdf differ diff --git a/nterchange/images/order.gif b/nterchange/images/order.gif new file mode 100644 index 0000000..de98ee7 Binary files /dev/null and b/nterchange/images/order.gif differ diff --git a/nterchange/images/otf/button.php b/nterchange/images/otf/button.php new file mode 100755 index 0000000..2af212e --- /dev/null +++ b/nterchange/images/otf/button.php @@ -0,0 +1,62 @@ + diff --git a/nterchange/images/otf/button/button_l.gif b/nterchange/images/otf/button/button_l.gif new file mode 100644 index 0000000..b6076a9 Binary files /dev/null and b/nterchange/images/otf/button/button_l.gif differ diff --git a/nterchange/images/otf/button/button_m.gif b/nterchange/images/otf/button/button_m.gif new file mode 100644 index 0000000..65d3cc5 Binary files /dev/null and b/nterchange/images/otf/button/button_m.gif differ diff --git a/nterchange/images/otf/button/button_r.gif b/nterchange/images/otf/button/button_r.gif new file mode 100644 index 0000000..9475abd Binary files /dev/null and b/nterchange/images/otf/button/button_r.gif differ diff --git a/nterchange/images/otf/fonts/mini7.ttf b/nterchange/images/otf/fonts/mini7.ttf new file mode 100644 index 0000000..eb9e91c Binary files /dev/null and b/nterchange/images/otf/fonts/mini7.ttf differ diff --git a/nterchange/images/remove.gif b/nterchange/images/remove.gif new file mode 100644 index 0000000..02d4d08 Binary files /dev/null and b/nterchange/images/remove.gif differ diff --git a/nterchange/images/s_asc.gif b/nterchange/images/s_asc.gif new file mode 100755 index 0000000..f1ad14f Binary files /dev/null and b/nterchange/images/s_asc.gif differ diff --git a/nterchange/images/s_desc.gif b/nterchange/images/s_desc.gif new file mode 100755 index 0000000..faef159 Binary files /dev/null and b/nterchange/images/s_desc.gif differ diff --git a/nterchange/images/shadow_bg.png b/nterchange/images/shadow_bg.png new file mode 100644 index 0000000..c928445 Binary files /dev/null and b/nterchange/images/shadow_bg.png differ diff --git a/nterchange/images/shadow_bl.gif b/nterchange/images/shadow_bl.gif new file mode 100644 index 0000000..480002c Binary files /dev/null and b/nterchange/images/shadow_bl.gif differ diff --git a/nterchange/images/shadow_br.gif b/nterchange/images/shadow_br.gif new file mode 100644 index 0000000..302b123 Binary files /dev/null and b/nterchange/images/shadow_br.gif differ diff --git a/nterchange/images/shadow_tr.gif b/nterchange/images/shadow_tr.gif new file mode 100644 index 0000000..e71f51f Binary files /dev/null and b/nterchange/images/shadow_tr.gif differ diff --git a/nterchange/images/site_admin_bg.gif b/nterchange/images/site_admin_bg.gif new file mode 100644 index 0000000..bacfb42 Binary files /dev/null and b/nterchange/images/site_admin_bg.gif differ diff --git a/nterchange/images/subtab_off.gif b/nterchange/images/subtab_off.gif new file mode 100644 index 0000000..b4b872e Binary files /dev/null and b/nterchange/images/subtab_off.gif differ diff --git a/nterchange/images/subtab_on.gif b/nterchange/images/subtab_on.gif new file mode 100644 index 0000000..a1a212e Binary files /dev/null and b/nterchange/images/subtab_on.gif differ diff --git a/nterchange/images/subtab_over.gif b/nterchange/images/subtab_over.gif new file mode 100644 index 0000000..53454d0 Binary files /dev/null and b/nterchange/images/subtab_over.gif differ diff --git a/nterchange/images/timed.gif b/nterchange/images/timed.gif new file mode 100644 index 0000000..9595ada Binary files /dev/null and b/nterchange/images/timed.gif differ diff --git a/nterchange/index.php b/nterchange/index.php new file mode 100644 index 0000000..713adca --- /dev/null +++ b/nterchange/index.php @@ -0,0 +1,3 @@ +"; + } catch(e) {} + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName.toUpperCase() != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array) || + arguments[1].tagName) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + } catch(e) {} + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName.toUpperCase() != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + + ATTR_MAP: { + 'className': 'class', + 'htmlFor': 'for' + }, + + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + + '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'"') + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(children.tagName) { + element.appendChild(children); + return; + } + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + }, + build: function(html) { + var element = this.node('div'); + $(element).update(html.strip()); + return element.down(); + }, + dump: function(scope) { + if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope + + var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + + "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + + "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ + "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ + "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ + "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); + + tags.each( function(tag){ + scope[tag] = function() { + return Builder.node.apply(Builder, [tag].concat($A(arguments))); + } + }); + } +} diff --git a/nterchange/javascripts/ckeditor/CHANGES.md b/nterchange/javascripts/ckeditor/CHANGES.md new file mode 100644 index 0000000..1813459 --- /dev/null +++ b/nterchange/javascripts/ckeditor/CHANGES.md @@ -0,0 +1,553 @@ +CKEditor 4 Changelog +==================== + +## CKEditor 4.4.1 + +New Features: + +* [#9661](http://dev.ckeditor.com/ticket/9661): Added the option to [configure](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-linkJavaScriptLinksAllowed) anchor tags with JavaScript code in `href` attribute. + +Fixed Issues: + +* [#11861](http://dev.ckeditor.com/ticket/11861): Fixed: [Webkit/Blink] Span elements created while joining adjacent elements. **Note:** This patch only covers cases when *Backspace* or *Delete* is pressed on a collapsed (empty) selection. The remaining case, with a non-empty selection, will be fixed in next release. +* [#10714](http://dev.ckeditor.com/ticket/10714): Fixed: [iOS] Selection and drop-downs are broken if touch listener is used due to [Webkit bug](https://bugs.webkit.org/show_bug.cgi?id=128924). Thanks to [Arty Gus](https://github.com/artygus)! +* [#11911](http://dev.ckeditor.com/ticket/11911): Fixed setting the `dir` attribute for preloaded language in [CKEDITOR.lang](http://docs.ckeditor.com/#!/api/CKEDITOR.lang). Thanks to [Akash Mohapatra](https://github.com/akashmohapatra)! +* [#11926](http://dev.ckeditor.com/ticket/11926): Fixed: Code snippet does not decode HTML entities when loading code from the `` element. +* [#11223](http://dev.ckeditor.com/ticket/11223): Fixed: Issue when [Protected Source](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-protectedSource) was not working in the title element. +* [#11859](http://dev.ckeditor.com/ticket/11859): Fixed: Removed [Source Dialog](http://ckeditor.com/addon/sourcedialog) plugin from being required in [Code Snippet](http://ckeditor.com/addon/codesnippet) sample. +* [#11754](http://dev.ckeditor.com/ticket/11754): Fixed: Infinite loop in Google Chrome when content contains not closed attributes. +* [#11848](http://dev.ckeditor.com/ticket/11848): Fixed: [`editor.insertElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) throwing an exception in IE when there was no selection in editor. +* [#11801](http://dev.ckeditor.com/ticket/11801): Fixed: Editor anchors unavailable when linking [Enhanced Image](http://ckeditor.com/addon/image2) widget. +* [#11626](http://dev.ckeditor.com/ticket/11626): Fixed: [Table Resize](http://ckeditor.com/addon/tableresize) sets invalid width. +* [#11872](http://dev.ckeditor.com/ticket/11872): Made [`element.addClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-addClass) chainable symmetrically to [`element.removeClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-removeClass). +* [#11813](http://dev.ckeditor.com/ticket/11813): Fixed: Link lost while pasting captioned image and restoring undo snapshot ([Enhanced Image](http://ckeditor.com/addon/image2)). +* [#11814](http://dev.ckeditor.com/ticket/11814): Fixed: _Link_ and _Unlink_ entries persistently displayed in [Enhanced Image](http://ckeditor.com/addon/image2) context menu. +* [#11839](http://dev.ckeditor.com/ticket/11839): Fixed: [IE9] Caret jumps out of editable area when resizing editor in source mode. +* [#11822](http://dev.ckeditor.com/ticket/11822): Fixed: [Webkit] Editing Anchors by double-click broken in some cases. +* [#11823](http://dev.ckeditor.com/ticket/11823): Fixed: [IE8] [Table Resize](http://ckeditor.com/addon/tableresize) throws error over scrollbar. +* [#11788](http://dev.ckeditor.com/ticket/11788): Fixed: It is not possible to change language back to _Not set_ in [Code Snippet](http://ckeditor.com/addon/codesnippet) dialog. +* [#11788](http://dev.ckeditor.com/ticket/11788): Fixed: [Filter](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.filter) rules are not applied inside elements with `contenteditable` attribute set to `true`. +* [#11798](http://dev.ckeditor.com/ticket/11798): Fixed: Inserting non-editable element inside a table cell breaks the table badly. +* [#11793](http://dev.ckeditor.com/ticket/11793): Fixed: Drop-down is not "on" when clicking it while editor is blurred. +* [#11850](http://dev.ckeditor.com/ticket/11850): Fixed: Fake objects with contenteditable set to `false` are not downcasted properly. +* [#11811](http://dev.ckeditor.com/ticket/11811): Fixed: Widget's data are not encoded correctly when passed to attribute. +* [#11777](http://dev.ckeditor.com/ticket/11777): Fixed encoding ampersand in the [Mathematical Formulas](http://ckeditor.com/addon/mathjax) plugin. +* [#11880](http://dev.ckeditor.com/ticket/11880): Fixed: [IE8-9] Linked image has a default thick border. + +Other changes: + +* [#11807](http://dev.ckeditor.com/ticket/11807): Updated jQuery version used in sample to 1.11.0 and tested CKEditor jQuery adapter with version 1.11.0 and 2.1.0. +* [#9504](http://dev.ckeditor.com/ticket/9504): Stopped using deprecated attribute.specified in all browsers except IE. +* [#11809](http://dev.ckeditor.com/ticket/11809): Changed tab size in `
      ` to 4 spaces.
      +
      +## CKEditor 4.4
      +
      +**Important Notes:**
      +
      +* Marked the [`editor.beforePaste`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-beforePaste) event as deprecated.
      +* The default class of captioned images has changed to `image` (was: `caption`). Please note that once edited in CKEditor 4.4+, all existing images of the `caption` class (`
      `) will be [filtered out](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) unless the [`config.image2_captionedClass`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_captionedClass) option is set to `caption`. For backward compatibility (i.e. when upgrading), it is highly recommended to use this setting, which also helps prevent CSS conflicts, etc. This does not apply to new CKEditor integrations. +* Widgets without defined buttons are no longer registered automatically to the [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter). Before CKEditor 4.4 widgets were registered to the ACF which was an incorrect behavior ([#11567](http://dev.ckeditor.com/ticket/11567)). This change should not have any impact on standard scenarios, but if your button does not execute the widget command, you need to set [`allowedContent`](http://docs.ckeditor.com/#!/api/CKEDITOR.feature-property-allowedContent) and [`requiredContent`](http://docs.ckeditor.com/#!/api/CKEDITOR.feature-property-requiredContent) properties for it manually, because the editor will not be able to find them. +* The [Show Borders](http://ckeditor.com/addon/showborders) plugin was added to the Standard installation package in order to ensure that unstyled tables are still visible for the user ([#11665](http://dev.ckeditor.com/ticket/11665)). +* Since CKEditor 4.4 the editor instance should be passed to [`CKEDITOR.style`](http://docs.ckeditor.com/#!/api/CKEDITOR.style) methods to ensure full compatibility with other features (e.g. applying styles to widgets requires that). We ensured backward compatibility though, so the [`CKEDITOR.style`](http://docs.ckeditor.com/#!/api/CKEDITOR.style) will work even when the editor instance is not provided. + +New Features: + +* [#11297](http://dev.ckeditor.com/ticket/11297): Styles can now be applied to widgets. The definition of a style which can be applied to a specific widget must contain two additional properties — `type` and `widget`. Read more in the [Widget Styles](http://docs.ckeditor.com/#!/guide/dev_styles-section-widget-styles) section of the "Syles Drop-down" guide. Note that by default, widgets support only classes and no other attributes or styles. Related changes and features: + * Introduced the [`CKEDITOR.style.addCustomHandler()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-static-method-addCustomHandler) method for registering custom style handlers. + * The [`CKEDITOR.style.apply()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-method-apply) and [`CKEDITOR.style.remove()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-method-remove) methods are now called with an editor instance instead of the document so they can be reused by the [`CKEDITOR.editor.applyStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-applyStyle) and [`CKEDITOR.editor.removeStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-removeStyle) methods. Backward compatibility was preserved, but from CKEditor 4.4 it is highly recommended to pass an editor instead of a document to these methods. + * Many new methods and properties were introduced in the [Widget API](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget) to make the handling of styles by widgets fully customizable. See: [`widget.definition.styleableElements`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.definition-property-styleableElements), [`widget.definition.styleToAllowedContentRule`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.definition-property-styleToAllowedContentRules), [`widget.addClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-addClass), [`widget.removeClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-removeClass), [`widget.getClasses()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-getClasses), [`widget.hasClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-hasClass), [`widget.applyStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-applyStyle), [`widget.removeStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-removeStyle), [`widget.checkStyleActive()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-checkStyleActive). + * Integration with the [Allowed Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) required an introduction of the [`CKEDITOR.style.toAllowedContent()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-method-toAllowedContentRules) method which can be implemented by the custom style handler and if exists, it is used by the [`CKEDITOR.filter`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter) to translate a style to [allowed content rules](http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules). +* [#11300](http://dev.ckeditor.com/ticket/11300): Various changes in the [Enhanced Image](http://ckeditor.com/addon/image2) plugin: + * Introduced the [`config.image2_captionedClass`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_captionedClass) option to configure the class of captioned images. + * Introduced the [`config.image2_alignClasses`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_alignClasses) option to configure the way images are aligned with CSS classes. + If this setting is defined, the editor produces classes instead of inline styles for aligned images. + * Default image caption can be translated (customized) with the `editor.lang.image2.captionPlaceholder` string. +* [#11341](http://dev.ckeditor.com/ticket/11341): [Enhanced Image](http://ckeditor.com/addon/image2) plugin: It is now possible to add a link to any image type. +* [#10202](http://dev.ckeditor.com/ticket/10202): Introduced wildcard support in the [Allowed Content Rules](http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules) format. +* [#10276](http://dev.ckeditor.com/ticket/10276): Introduced blacklisting in the [Allowed Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter). +* [#10480](http://dev.ckeditor.com/ticket/10480): Introduced code snippets with code highlighting. There are two versions available so far — the default [Code Snippet](http://ckeditor.com/addon/codesnippet) which uses the [highlight.js](http://highlightjs.org) library and the [Code Snippet GeSHi](http://ckeditor.com/addon/codesnippetgeshi) which uses the [GeSHi](http://qbnz.com/highlighter/) library. +* [#11737](http://dev.ckeditor.com/ticket/11737): Introduced an option to prevent [filtering](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) of an element that matches custom criteria (see [`filter.addElementCallback()`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter-method-addElementCallback)). +* [#11532](http://dev.ckeditor.com/ticket/11532): Introduced the [`editor.addContentsCss()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-addContentsCss) method that can be used for [adding custom CSS files](http://docs.ckeditor.com/#!/guide/plugin_sdk_styles). +* [#11536](http://dev.ckeditor.com/ticket/11536): Added the [`CKEDITOR.tools.htmlDecode()`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-htmlDecode) method for decoding HTML entities. +* [#11225](http://dev.ckeditor.com/ticket/11225): Introduced the [`CKEDITOR.tools.transparentImageData`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-property-transparentImageData) property which contains transparent image data to be used in CSS or as image source. + +Other changes: + +* [#11377](http://dev.ckeditor.com/ticket/11377): Unified internal representation of empty anchors using the [fake objects](http://ckeditor.com/addon/fakeobjects). +* [#11422](http://dev.ckeditor.com/ticket/11422): Removed Firefox 3.x, Internet Explorer 6 and Opera 12.x leftovers in code. +* [#5217](http://dev.ckeditor.com/ticket/5217): Setting data (including switching between modes) creates a new undo snapshot. Besides that: + * Introduced the [`editable.status`](http://docs.ckeditor.com/#!/api/CKEDITOR.editable-property-status) property. + * Introduced a new `forceUpdate` option for the [`editor.lockSnapshot`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-lockSnapshot) event. + * Fixed: Selection not being unlocked in inline editor after setting data ([#11500](http://dev.ckeditor.com/ticket/11500)). +* The [WebSpellChecker](http://ckeditor.com/addon/wsc) plugin was updated to the latest version. + +Fixed Issues: + +* [#10190](http://dev.ckeditor.com/ticket/10190): Fixed: Removing block style with [`editor.removeStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-removeStyle) should result in a paragraph and not a div. +* [#11727](http://dev.ckeditor.com/ticket/11727): Fixed: The editor tries to select a non-editable image which was clicked. + +## CKEditor 4.3.5 + +New Features: + +* Added new translation: Tatar. + +Fixed Issues: + +* [#11677](http://dev.ckeditor.com/ticket/11677): Fixed: Undo/Redo keystrokes are blocked in the source mode. +* [#11717](http://dev.ckeditor.com/ticket/11717): [Document Properties](http://ckeditor.com/addon/docprops) plugin requires the [Color Dialog](http://ckeditor.com/addon/colordialog) plugin to work. + +## CKEditor 4.3.4 + +Fixed Issues: + +* [#11597](http://dev.ckeditor.com/ticket/11597): [IE11] Fixed: Error thrown when trying to open the [preview](http://ckeditor.com/addon/preview) using the keyboard. +* [#11544](http://dev.ckeditor.com/ticket/11544): [Placeholders](http://ckeditor.com/addon/placeholder) will no longer be upcasted in parents not accepting `` elements. +* [#8663](http://dev.ckeditor.com/ticket/8663): Fixed [`element.renameNode()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-renameNode) not clearing the [`element.getName()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-getName) cache. +* [#11574](http://dev.ckeditor.com/ticket/11574): Fixed: *Backspace* destroying the DOM structure if an inline editable is placed in a list item. +* [#11603](http://dev.ckeditor.com/ticket/11603): Fixed: [Table Resize](http://ckeditor.com/addon/tableresize) attaches to tables outside the editable. +* [#9205](http://dev.ckeditor.com/ticket/9205), [#7805](http://dev.ckeditor.com/ticket/7805), [#8216](http://dev.ckeditor.com/ticket/8216): Fixed: `{cke_protected_1}` appearing in data in various cases where HTML comments are placed next to `"` or `'`. +* [#11635](http://dev.ckeditor.com/ticket/11635): Fixed: Some attributes are not protected before the content is passed through the fix bin. +* [#11660](http://dev.ckeditor.com/ticket/11660): [IE] Fixed: Table content is lost when some extra markup is inside the table. +* [#11641](http://dev.ckeditor.com/ticket/11641): Fixed: Switching between modes in the classic editor removes content styles for the inline editor. +* [#11568](http://dev.ckeditor.com/ticket/11568): Fixed: [Styles](http://ckeditor.com/addon/stylescombo) drop-down list is not enabled on selection change. + +## CKEditor 4.3.3 + +Fixed Issues: + +* [#11500](http://dev.ckeditor.com/ticket/11500): [Webkit/Blink] Fixed: Selection lost when setting data in another inline editor. Additionally, [`selection.removeAllRanges()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-method-removeAllRanges) is now scoped to selection's [root](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-property-root). +* [#11104](http://dev.ckeditor.com/ticket/11104): [IE] Fixed: Various issues with scrolling and selection when focusing widgets. +* [#11487](http://dev.ckeditor.com/ticket/11487): Moving mouse over the [Enhanced Image](http://ckeditor.com/addon/image2) widget will no longer change the value returned by the [`editor.checkDirty()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-checkDirty) method. +* [#8673](http://dev.ckeditor.com/ticket/8673): [WebKit] Fixed: Cannot select and remove the [Page Break](http://ckeditor.com/addon/pagebreak). +* [#11413](http://dev.ckeditor.com/ticket/11413): Fixed: Incorrect [`editor.execCommand()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-execCommand) behavior. +* [#11438](http://dev.ckeditor.com/ticket/11438): Splitting table cells vertically is no longer changing table structure. +* [#8899](http://dev.ckeditor.com/ticket/8899): Fixed: Links in the [About CKEditor](http://ckeditor.com/addon/about) dialog window now open in a new browser window or tab. +* [#11490](http://dev.ckeditor.com/ticket/11490): Fixed: [Menu button](http://ckeditor.com/addon/menubutton) panel not showing in the source mode. +* [#11417](http://dev.ckeditor.com/ticket/11417): The [`widget.doubleclick`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-event-doubleclick) event is not canceled anymore after editing was triggered. +* [#11253](http://dev.ckeditor.com/ticket/11253): [IE] Fixed: Clipped upload button in the [Enhanced Image](http://ckeditor.com/addon/image2) dialog window. +* [#11359](http://dev.ckeditor.com/ticket/11359): Standardized the way anchors are discovered by the [Link](http://ckeditor.com/addon/link) plugin. +* [#11058](http://dev.ckeditor.com/ticket/11058): [IE8] Fixed: Error when deleting a table row. +* [#11508](http://dev.ckeditor.com/ticket/11508): Fixed: [`htmlDataProcessor`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor) discovering protected attributes within other attributes' values. +* [#11533](http://dev.ckeditor.com/ticket/11533): Widgets: Avoid recurring upcasts if the DOM structure was modified during an upcast. +* [#11400](http://dev.ckeditor.com/ticket/11400): Fixed: The [`domObject.removeAllListeners()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.domObject-method-removeAllListeners) method does not remove custom listeners completely. +* [#11493](http://dev.ckeditor.com/ticket/11493): Fixed: The [`selection.getRanges()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-method-getRanges) method does not override cached ranges when used with the `onlyEditables` argument. +* [#11390](http://dev.ckeditor.com/ticket/11390): [IE] All [XML](http://ckeditor.com/addon/xml) plugin [methods](http://docs.ckeditor.com/#!/api/CKEDITOR.xml) now work in IE10+. +* [#11542](http://dev.ckeditor.com/ticket/11542): [IE11] Fixed: Blurry toolbar icons when Right-to-Left UI language is set. +* [#11504](http://dev.ckeditor.com/ticket/11504): Fixed: When [`config.fullPage`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-fullPage) is set to `true`, entities are not encoded in editor output. +* [#11004](http://dev.ckeditor.com/ticket/11004): Integrated [Enhanced Image](http://ckeditor.com/addon/image2) dialog window with [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter). +* [#11439](http://dev.ckeditor.com/ticket/11439): Fixed: Properties get cloned in the Cell Properties dialog window if multiple cells are selected. + +## CKEditor 4.3.2 + +Fixed Issues: + +* [#11331](http://dev.ckeditor.com/ticket/11331): A menu button will have a changed label when selected instead of using the `aria-pressed` attribute. +* [#11177](http://dev.ckeditor.com/ticket/11177): Widget drag handler improvements: + * [#11176](http://dev.ckeditor.com/ticket/11176): Fixed: Initial position is not updated when the widget data object is empty. + * [#11001](http://dev.ckeditor.com/ticket/11001): Fixed: Multiple synchronous layout recalculations are caused by initial drag handler positioning causing performance issues. + * [#11161](http://dev.ckeditor.com/ticket/11161): Fixed: Drag handler is not repositioned in various situations. + * [#11281](http://dev.ckeditor.com/ticket/11281): Fixed: Drag handler and mask are duplicated after widget reinitialization. +* [#11207](http://dev.ckeditor.com/ticket/11207): [Firefox] Fixed: Misplaced [Enhanced Image](http://ckeditor.com/addon/image2) resizer in the inline editor. +* [#11102](http://dev.ckeditor.com/ticket/11102): `CKEDITOR.template` improvements: + * [#11102](http://dev.ckeditor.com/ticket/11102): Added newline character support. + * [#11216](http://dev.ckeditor.com/ticket/11216): Added "\\'" substring support. +* [#11121](http://dev.ckeditor.com/ticket/11121): [Firefox] Fixed: High Contrast mode is enabled when the editor is loaded in a hidden iframe. +* [#11350](http://dev.ckeditor.com/ticket/11350): The default value of [`config.contentsCss`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-contentsCss) is affected by [`CKEDITOR.getUrl()`](http://docs.ckeditor.com/#!/api/CKEDITOR-method-getUrl). +* [#11097](http://dev.ckeditor.com/ticket/11097): Improved the [Autogrow](http://ckeditor.com/addon/autogrow) plugin performance when dealing with very big tables. +* [#11290](http://dev.ckeditor.com/ticket/11290): Removed redundant code in the [Source Dialog](http://ckeditor.com/addon/sourcedialog) plugin. +* [#11133](http://dev.ckeditor.com/ticket/11133): [Page Break](http://ckeditor.com/addon/pagebreak) becomes editable if pasted. +* [#11126](http://dev.ckeditor.com/ticket/11126): Fixed: Native Undo executed once the bottom of the snapshot stack is reached. +* [#11131](http://dev.ckeditor.com/ticket/11131): [Div Editing Area](http://ckeditor.com/addon/divarea): Fixed: Error thrown when switching to source mode if the selection was in widget's nested editable. +* [#11139](http://dev.ckeditor.com/ticket/11139): [Div Editing Area](http://ckeditor.com/addon/divarea): Fixed: Elements Path is not cleared after switching to source mode. +* [#10778](http://dev.ckeditor.com/ticket/10778): Fixed a bug with range enlargement. The range no longer expands to visible whitespace. +* [#11146](http://dev.ckeditor.com/ticket/11146): [IE] Fixed: Preview window switches Internet Explorer to Quirks Mode. +* [#10762](http://dev.ckeditor.com/ticket/10762): [IE] Fixed: JavaScript code displayed in preview window's URL bar. +* [#11186](http://dev.ckeditor.com/ticket/11186): Introduced the [`widgets.repository.addUpcastCallback()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-addUpcastCallback) method that allows to block upcasting given element to a widget. +* [#11307](http://dev.ckeditor.com/ticket/11307): Fixed: Paste as Plain Text conflict with the [MooTools](http://mootools.net) library. +* [#11140](http://dev.ckeditor.com/ticket/11140): [IE11] Fixed: Anchors are not draggable. +* [#11379](http://dev.ckeditor.com/ticket/11379): Changed default contents `line-height` to unitless values to avoid huge text overlapping (like in [#9696](http://dev.ckeditor.com/ticket/9696)). +* [#10787](http://dev.ckeditor.com/ticket/10787): [Firefox] Fixed: Broken replacement of text while pasting into `div`-based editor. +* [#10884](http://dev.ckeditor.com/ticket/10884): Widgets integration with the [Show Blocks](http://ckeditor.com/addon/showblocks) plugin. +* [#11021](http://dev.ckeditor.com/ticket/11021): Fixed: An error thrown when selecting entire editable contents while fake selection is on. +* [#11086](http://dev.ckeditor.com/ticket/11086): [IE8] Re-enable inline widgets drag&drop in Internet Explorer 8. +* [#11372](http://dev.ckeditor.com/ticket/11372): Widgets: Special characters encoded twice in nested editables. +* [#10068](http://dev.ckeditor.com/ticket/10068): Fixed: Support for protocol-relative URLs. +* [#11283](http://dev.ckeditor.com/ticket/11283): [Enhanced Image](http://ckeditor.com/addon/image2): A `
      ` element with `text-align: center` and an image inside is not recognised correctly. +* [#11196](http://dev.ckeditor.com/ticket/11196): [Accessibility Instructions](http://ckeditor.com/addon/a11yhelp): Allowed additional keyboard button labels to be translated in the dialog window. + +## CKEditor 4.3.1 + +**Important Notes:** + +* To match the naming convention, the `language` button is now `Language` ([#11201](http://dev.ckeditor.com/ticket/11201)). +* [Enhanced Image](http://ckeditor.com/addon/image2) button, context menu, command, and icon names match those of the [Image](http://ckeditor.com/addon/image) plugin ([#11222](http://dev.ckeditor.com/ticket/11222)). + +Fixed Issues: + +* [#11244](http://dev.ckeditor.com/ticket/11244): Changed: The [`widget.repository.checkWidgets()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-checkWidgets) method now fires the [`widget.repository.checkWidgets`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-event-checkWidgets) event, so from CKEditor 4.3.1 it is preferred to use the method rather than fire the event. +* [#11171](http://dev.ckeditor.com/ticket/11171): Fixed: [`editor.insertElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) and [`editor.insertText()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertText) methods do not call the [`widget.repository.checkWidgets()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-checkWidgets) method. +* [#11085](http://dev.ckeditor.com/ticket/11085): [IE8] Replaced preview generated by the [Mathematical Formulas](http://ckeditor.com/addon/mathjax) widget with a placeholder. +* [#11044](http://dev.ckeditor.com/ticket/11044): Enhanced WAI-ARIA support for the [Language](http://ckeditor.com/addon/language) plugin drop-down menu. +* [#11075](http://dev.ckeditor.com/ticket/11075): With drop-down menu button focused, pressing the *Down Arrow* key will now open the menu and focus its first option. +* [#11165](http://dev.ckeditor.com/ticket/11165): Fixed: The [File Browser](http://ckeditor.com/addon/filebrowser) plugin cannot be removed from the editor. +* [#11159](http://dev.ckeditor.com/ticket/11159): [IE9-10] [Enhanced Image](http://ckeditor.com/addon/image2): Fixed buggy discovery of image dimensions. +* [#11101](http://dev.ckeditor.com/ticket/11101): Drop-down lists no longer break when given double quotes. +* [#11077](http://dev.ckeditor.com/ticket/11077): [Enhanced Image](http://ckeditor.com/addon/image2): Empty undo step recorded when resizing the image. +* [#10853](http://dev.ckeditor.com/ticket/10853): [Enhanced Image](http://ckeditor.com/addon/image2): Widget has paragraph wrapper when de-captioning unaligned image. +* [#11198](http://dev.ckeditor.com/ticket/11198): Widgets: Drag handler is not fully visible when an inline widget is in a heading. +* [#11132](http://dev.ckeditor.com/ticket/11132): [Firefox] Fixed: Caret is lost after drag and drop of an inline widget. +* [#11182](http://dev.ckeditor.com/ticket/11182): [IE10-11] Fixed: Editor crashes (IE11) or works with minor issues (IE10) if a page is loaded in Quirks Mode. See [`env.quirks`](http://docs.ckeditor.com/#!/api/CKEDITOR.env-property-quirks) for more details. +* [#11204](http://dev.ckeditor.com/ticket/11204): Added `figure` and `figcaption` styles to the `contents.css` file so [Enhanced Image](http://ckeditor.com/addon/image2) looks nicer. +* [#11202](http://dev.ckeditor.com/ticket/11202): Fixed: No newline in [BBCode](http://ckeditor.com/addon/bbcode) mode. +* [#10890](http://dev.ckeditor.com/ticket/10890): Fixed: Error thrown when pressing the *Delete* key in a list item. +* [#10055](http://dev.ckeditor.com/ticket/10055): [IE8-10] Fixed: *Delete* pressed on a selected image causes the browser to go back. +* [#11183](http://dev.ckeditor.com/ticket/11183): Fixed: Inserting a horizontal rule or a table in multiple row selection causes a browser crash. Additionally, the [`editor.insertElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) method does not insert the element into every range of a selection any more. +* [#11042](http://dev.ckeditor.com/ticket/11042): Fixed: Selection made on an element containing a non-editable element was not auto faked. +* [#11125](http://dev.ckeditor.com/ticket/11125): Fixed: Keyboard navigation through menu and drop-down items will now cycle. +* [#11011](http://dev.ckeditor.com/ticket/11011): Fixed: The [`editor.applyStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-applyStyle) method removes attributes from nested elements. +* [#11179](http://dev.ckeditor.com/ticket/11179): Fixed: [`editor.destroy()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-destroy) does not cleanup content generated by the [Table Resize](http://ckeditor.com/addon/tableresize) plugin for inline editors. +* [#11237](http://dev.ckeditor.com/ticket/11237): Fixed: Table border attribute value is deleted when pasting content from Microsoft Word. +* [#11250](http://dev.ckeditor.com/ticket/11250): Fixed: HTML entities inside the `");return""+encodeURIComponent(a)+""})}function s(a){return a.replace(t,function(a,b){return decodeURIComponent(b)})}function r(a){return a.replace(/<\!--(?!{cke_protected})[\s\S]+?--\>/g, +function(a){return"<\!--"+A+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\>"})}function v(a){return a.replace(/<\!--\{cke_protected\}\{C\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)})}function g(a,b){var c=b._.dataStore;return a.replace(/<\!--\{cke_protected\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return c&&c[b]||""})}function p(a,b){for(var c=[],d=b.config.protectedSource,f=b._.dataStore||(b._.dataStore= +{id:1}),e=/<\!--\{cke_temp(comment)?\}(\d*?)--\>/g,d=[//gi,//gi].concat(d),a=a.replace(/<\!--[\s\S]*?--\>/g,function(a){return"<\!--{cke_tempcomment}"+(c.push(a)-1)+"--\>"}),j=0;j"});a=a.replace(e,function(a,b,d){return"<\!--"+A+(b?"{C}":"")+encodeURIComponent(c[d]).replace(/--/g,"%2D%2D")+ +"--\>"});a=a.replace(/<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=>]+))+\s*>/g,function(a){return a.replace(/<\!--\{cke_protected\}([^>]*)--\>/g,function(a,b){f[f.id]=decodeURIComponent(b);return"{cke_protected_"+f.id++ +"}"})});return a=a.replace(/<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g,function(a,c,d,f){return"<"+c+d+">"+g(v(f),b)+""})}CKEDITOR.htmlDataProcessor=function(b){var c,d,f=this;this.editor=b;this.dataFilter=c=new CKEDITOR.htmlParser.filter;this.htmlFilter= +d=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;c.addRules(u);c.addRules(P,{applyToAll:true});c.addRules(a(b,"data"),{applyToAll:true});d.addRules(q);d.addRules(L,{applyToAll:true});d.addRules(a(b,"html"),{applyToAll:true});b.on("toHtml",function(a){var a=a.data,c=a.dataValue,c=p(c,b),c=o(c,F),c=k(c),c=o(c,J),c=c.replace(x,"$1cke:$2"),c=c.replace(B,""),c=c.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),c=c.replace(/([^a-z0-9<\-])(on\w{3,})(?!>)/gi, +"$1data-cke-"+CKEDITOR.rnd+"-$2"),d=a.context||b.editable().getName(),f;if(CKEDITOR.env.ie&&CKEDITOR.env.version<9&&d=="pre"){d="div";c="
      "+c+"
      ";f=1}d=b.document.createElement(d);d.setHtml("a"+c);c=d.getHtml().substr(1);c=c.replace(RegExp("data-cke-"+CKEDITOR.rnd+"-","ig"),"");f&&(c=c.replace(/^
      |<\/pre>$/gi,""));c=c.replace(y,"$1$2");c=s(c);c=v(c);a.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.context,a.fixForBody===false?false:e(a.enterMode,b.config.autoParagraph))},null,null,
      +5);b.on("toHtml",function(a){a.data.filter.applyTo(a.data.dataValue,true,a.data.dontFilter,a.data.enterMode)&&b.fire("dataFiltered")},null,null,6);b.on("toHtml",function(a){a.data.dataValue.filterChildren(f.dataFilter,true)},null,null,10);b.on("toHtml",function(a){var a=a.data,b=a.dataValue,c=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(c);b=c.getHtml(true);a.dataValue=r(b)},null,null,15);b.on("toDataFormat",function(a){var c=a.data.dataValue;a.data.enterMode!=CKEDITOR.ENTER_BR&&(c=c.replace(/^
      /i, +""));a.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.data.context,e(a.data.enterMode,b.config.autoParagraph))},null,null,5);b.on("toDataFormat",function(a){a.data.dataValue.filterChildren(f.htmlFilter,true)},null,null,10);b.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,false,true)},null,null,11);b.on("toDataFormat",function(a){var c=a.data.dataValue,d=f.writer;d.reset();c.writeChildrenHtml(d);c=d.getHtml(true);c=v(c);c=g(c,b);a.data.dataValue=c},null,null,15)};CKEDITOR.htmlDataProcessor.prototype= +{toHtml:function(a,b,c,d){var f=this.editor,e,g,j;if(b&&typeof b=="object"){e=b.context;c=b.fixForBody;d=b.dontFilter;g=b.filter;j=b.enterMode}else e=b;!e&&e!==null&&(e=f.editable().getName());return f.fire("toHtml",{dataValue:a,context:e,fixForBody:c,dontFilter:d,filter:g||f.filter,enterMode:j||f.enterMode}).dataValue},toDataFormat:function(a,b){var c,d,f;if(b){c=b.context;d=b.filter;f=b.enterMode}!c&&c!==null&&(c=this.editor.editable().getName());return this.editor.fire("toDataFormat",{dataValue:a, +filter:d||this.editor.filter,context:c,enterMode:f||this.editor.enterMode}).dataValue}};var z=/(?: |\xa0)$/,A="{cke_protected}",l=CKEDITOR.dtd,j=["caption","colgroup","col","thead","tfoot","tbody"],m=CKEDITOR.tools.extend({},l.$blockLimit,l.$block),u={elements:{input:n,textarea:n}},P={attributeNames:[[/^on/,"data-cke-pa-on"],[/^data-cke-expando$/,""]]},q={elements:{embed:function(a){var b=a.parent;if(b&&b.name=="object"){var c=b.attributes.width,b=b.attributes.height;if(c)a.attributes.width= +c;if(b)a.attributes.height=b}},a:function(a){if(!a.children.length&&!a.attributes.name&&!a.attributes["data-cke-saved-name"])return false}}},L={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/,""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return false;for(var c=["name","href","src"],d,f=0;f-1&&d>-1&&c!=d)){c=a.parent?a.getIndex():-1;d=b.parent?b.getIndex():-1}return c>d?1:-1})},param:function(a){a.children=[];a.isEmpty=true;return a},span:function(a){a.attributes["class"]=="Apple-style-span"&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable}, +style:function(a){var b=a.children[0];if(b&&b.value)b.value=CKEDITOR.tools.trim(b.value);if(!a.attributes.type)a.attributes.type="text/css"},title:function(a){var b=a.children[0];!b&&h(a,b=new CKEDITOR.htmlParser.text);b.value=a.attributes["data-cke-title"]||""},input:i,textarea:i},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,""))||false}}};if(CKEDITOR.env.ie)L.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})}; +var H=/<(a|area|img|input|source)\b([^>]*)>/gi,G=/([\w-]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,C=/^(href|src|name)$/i,J=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,F=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,t=/([^<]*)<\/cke:encoded>/gi,x=/(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,y=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,B=/]*?)\/?>(?!\s*<\/cke:\1)/gi})();"use strict"; +CKEDITOR.htmlParser.element=function(a,e){this.name=a;this.attributes=e||{};this.children=[];var b=a||"",c=b.match(/^cke:(.*)/);c&&(b=c[1]);b=!(!CKEDITOR.dtd.$nonBodyContent[b]&&!CKEDITOR.dtd.$block[b]&&!CKEDITOR.dtd.$listItem[b]&&!CKEDITOR.dtd.$tableContent[b]&&!(CKEDITOR.dtd.$nonEditable[b]||b=="br"));this.isEmpty=!!CKEDITOR.dtd.$empty[a];this.isUnknown=!CKEDITOR.dtd[a];this._={isBlockLike:b,hasInlineStarted:this.isEmpty||!b}}; +CKEDITOR.htmlParser.cssStyle=function(a){var e={};((a instanceof CKEDITOR.htmlParser.element?a.attributes.style:a)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,c,d){c=="font-family"&&(d=d.replace(/["']/g,""));e[c.toLowerCase()]=d});return{rules:e,populate:function(a){var c=this.toString();if(c)a instanceof CKEDITOR.dom.element?a.setAttribute("style",c):a instanceof CKEDITOR.htmlParser.element?a.attributes.style=c:a.style=c},toString:function(){var a=[],c; +for(c in e)e[c]&&a.push(c,":",e[c],";");return a.join("")}}}; +(function(){function a(a){return function(b){return b.type==CKEDITOR.NODE_ELEMENT&&(typeof a=="string"?b.name==a:b.name in a)}}var e=function(a,b){a=a[0];b=b[0];return ab?1:0},b=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:b.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(a,b){var f=this,e,n,b=f.getFilterContext(b);if(b.off)return true; +if(!f.parent)a.onRoot(b,f);for(;;){e=f.name;if(!(n=a.onElementName(b,e))){this.remove();return false}f.name=n;if(!(f=a.onElement(b,f))){this.remove();return false}if(f!==this){this.replaceWith(f);return false}if(f.name==e)break;if(f.type!=CKEDITOR.NODE_ELEMENT){this.replaceWith(f);return false}if(!f.name){this.replaceWithChildren();return false}}e=f.attributes;var i,k;for(i in e){k=i;for(n=e[i];;)if(k=a.onAttributeName(b,i))if(k!=i){delete e[i];i=k}else break;else{delete e[i];break}k&&((n=a.onAttribute(b, +f,k,n))===false?delete e[k]:e[k]=n)}f.isEmpty||this.filterChildren(a,false,b);return true},filterChildren:b.filterChildren,writeHtml:function(a,b){b&&this.filter(b);var f=this.name,h=[],n=this.attributes,i,k;a.openTag(f,n);for(i in n)h.push([i,n[i]]);a.sortAttributes&&h.sort(e);i=0;for(k=h.length;i0)this.children[a-1].next=null;this.parent.add(f,this.getIndex()+1);return f},addClass:function(a){if(!this.hasClass(a)){var b=this.attributes["class"]||"";this.attributes["class"]=b+(b?" ":"")+ +a}},removeClass:function(a){var b=this.attributes["class"];if(b)(b=CKEDITOR.tools.trim(b.replace(RegExp("(?:\\s+|^)"+a+"(?:\\s+|$)")," ")))?this.attributes["class"]=b:delete this.attributes["class"]},hasClass:function(a){var b=this.attributes["class"];return!b?false:RegExp("(?:^|\\s)"+a+"(?=\\s|$)").test(b)},getFilterContext:function(a){var b=[];a||(a={off:false,nonEditable:false,nestedEditable:false});!a.off&&this.attributes["data-cke-processor"]=="off"&&b.push("off",true);!a.nonEditable&&this.attributes.contenteditable== +"false"?b.push("nonEditable",true):a.nonEditable&&(!a.nestedEditable&&this.attributes.contenteditable=="true")&&b.push("nestedEditable",true);if(b.length)for(var a=CKEDITOR.tools.copy(a),f=0;f'+c.getValue()+"
      ",CKEDITOR.document); +a.insertAfter(c);c.hide();c.$.form&&b._attachToForm()}else b.setData(a.getHtml(),null,true);b.on("loaded",function(){b.fire("uiReady");b.editable(a);b.container=a;b.setData(b.getData(1));b.resetDirty();b.fire("contentDom");b.mode="wysiwyg";b.fire("mode");b.status="ready";b.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,b)},null,null,1E4);b.on("destroy",function(){if(c){b.container.clearCustomData();b.container.remove();c.show()}b.element.clearCustomData();delete b.element});return b}; +CKEDITOR.inlineAll=function(){var a,e,b;for(b in CKEDITOR.dtd.$editable)for(var c=CKEDITOR.document.getElementsByTag(b),d=0,f=c.count();d{voiceLabel}<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation">{bottomHtml}')); +b=CKEDITOR.dom.element.createFromHtml(c.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:[a.lang.editor,a.name].join(", "),topHtml:i?''+i+"":"",contentId:a.ui.spaceId("contents"),bottomHtml:k?''+k+"":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(n==CKEDITOR.ELEMENT_MODE_REPLACE){e.hide(); +b.insertAfter(e)}else e.append(b);a.container=b;i&&a.ui.space("top").unselectable();k&&a.ui.space("bottom").unselectable();e=a.config.width;n=a.config.height;e&&b.setStyle("width",CKEDITOR.tools.cssLength(e));n&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(n));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(b,c){return a(b,c,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(b,c,e){return a(b, +c,e,CKEDITOR.ELEMENT_MODE_APPENDTO)};CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b",m="",a=f+a.replace(e,function(){return m+f})+m}a=a.replace(/\n/g,"
      ");b||(a=a.replace(RegExp("
      (?=)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g," ");a=a.replace(/(>|\s) /g,function(a,b){return b+" "}).replace(/ (?=<)/g," ");r(this,"text",a)},insertElement:function(a,b){b?this.insertElementIntoRange(a,b):this.insertElementIntoSelection(a)},insertElementIntoRange:function(a, +b){var c=this.editor,d=c.config.enterMode,e=a.getName(),f=CKEDITOR.dtd.$block[e];if(b.checkReadOnly())return false;b.deleteContents(1);b.startContainer.type==CKEDITOR.NODE_ELEMENT&&b.startContainer.is({tr:1,table:1,tbody:1,thead:1,tfoot:1})&&v(b);var m,h;if(f)for(;(m=b.getCommonAncestor(0,1))&&(h=CKEDITOR.dtd[m.getName()])&&(!h||!h[e]);)if(m.getName()in CKEDITOR.dtd.span)b.splitElement(m);else if(b.checkStartOfBlock()&&b.checkEndOfBlock()){b.setStartBefore(m);b.collapse(true);m.remove()}else b.splitBlock(d== +CKEDITOR.ENTER_DIV?"div":"p",c.editable());b.insertNode(a);return true},insertElementIntoSelection:function(a){h(this);var b=this.editor,d=b.activeEnterMode,b=b.getSelection(),e=b.getRanges()[0],f=a.getName(),f=CKEDITOR.dtd.$block[f];if(this.insertElementIntoRange(a,e)){e.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);if(f)if((f=a.getNext(function(a){return c(a)&&!i(a)}))&&f.type==CKEDITOR.NODE_ELEMENT&&f.is(CKEDITOR.dtd.$block))f.getDtd()["#"]?e.moveToElementEditStart(f):e.moveToElementEditEnd(a); +else if(!f&&d!=CKEDITOR.ENTER_BR){f=e.fixBlock(true,d==CKEDITOR.ENTER_DIV?"div":"p");e.moveToElementEditStart(f)}}b.selectRanges([e]);n(this)},setData:function(a,b){b||(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);if(this.status=="unloaded")this.status="ready";this.editor.fire("dataReady")},getData:function(a){var b=this.getHtml();a||(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable",!a)},detach:function(){this.removeClass("cke_editable"); +this.status="detached";var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||a.config.ignoreEmptyParagraph!==false&&(b=b.replace(k,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData", +function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)}, +this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE||a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus=false},null,null,-1);this.on("focus",function(){this.hasFocus= +true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus=true;a.once("contentDom",function(){a.focusManager.focus()})}this.isInline()&&this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var d=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var e=a.config.contentsLangDirection;this.getDirection(1)!=e&&this.changeAttr("dir",e);var h=CKEDITOR.getCss();if(h){e= +d.getHead();if(!e.getCustomData("stylesheet")){h=d.appendStyleText(h);h=new CKEDITOR.dom.element(h.ownerNode||h.owningElement);e.setCustomData("stylesheet",h);h.data("cke-temp",1)}}e=d.getCustomData("stylesheet_ref")||0;d.setCustomData("stylesheet_ref",e+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()}); +var l={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.domEvent.getKey(),d;if(c in l){var b=a.getSelection(),e,q=b.getRanges()[0],h=q.startPath(),i,k,p,c=c==8;if(CKEDITOR.env.ie&&CKEDITOR.env.version<11&&(e=b.getSelectedElement())||(e=f(b))){a.fire("saveSnapshot");q.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START);e.remove();q.select();a.fire("saveSnapshot");d=1}else if(q.collapsed)if((i=h.block)&&(p=i[c?"getPrevious":"getNext"](o))&&p.type==CKEDITOR.NODE_ELEMENT&& +p.is("table")&&q[c?"checkStartOfBlock":"checkEndOfBlock"]()){a.fire("saveSnapshot");q[c?"checkEndOfBlock":"checkStartOfBlock"]()&&i.remove();q["moveToElementEdit"+(c?"End":"Start")](p);q.select();a.fire("saveSnapshot");d=1}else if(h.blockLimit&&h.blockLimit.is("td")&&(k=h.blockLimit.getAscendant("table"))&&q.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(p=k[c?"getPrevious":"getNext"](o))){a.fire("saveSnapshot");q["moveToElementEdit"+(c?"End":"Start")](p);q.checkStartOfBlock()&&q.checkEndOfBlock()? +p.remove():q.select();a.fire("saveSnapshot");d=1}else if((k=h.contains(["td","th","caption"]))&&q.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END))d=1}return!d});a.blockless&&(CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)&&this.attachListener(this,"keyup",function(b){if(b.data.getKeystroke()in l&&!this.getFirst(c)){this.appendBogus();b=a.createRange();b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START);b.select()}});this.attachListener(this,"dblclick",function(b){if(a.readOnly)return false; +b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie||this.attachListener(this,"mousedown",function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")&&!c.isReadOnly()){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button==2){b=b.data.getTarget();if(!b.getOuterHtml().replace(k, +"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()});this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()})}CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){b=b.data.domEvent.getKey();if(b in l){var b=b==8,c=a.getSelection(),d=c.getRanges()[0],e=d.startPath(),f=e.block;if(d.collapsed&& +f&&d[b?"checkStartOfBlock":"checkEndOfBlock"]()&&d.moveToClosestEditablePosition(f,!b)&&d.collapsed){if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var h=d.startContainer.getChild(d.startOffset-(b?1:0));if(h&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("hr")){a.fire("saveSnapshot");h.remove();a.fire("saveSnapshot");return false}}if((d=d.startPath().block)&&(!d||!d.contains(f))){a.fire("saveSnapshot");for(var i=f.getCommonAncestor(d),k=b?f:d,h=k;(k=k.getParent())&&!i.equals(k)&&k.getChildCount()==1;)h=k; +var p;(p=(b?d:f).getBogus())&&p.remove();p=c.createBookmarks();(b?f:d).moveChildren(b?d:f,false);e.lastElement.mergeSiblings();h.remove();c.selectBookmarks(p);a.fire("saveSnapshot");return false}}}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c= +a.getCustomData("stylesheet_ref");if(--c)a.setCustomData("stylesheet_ref",c);else{a.removeCustomData("stylesheet_ref");b.removeCustomData("stylesheet").remove()}}}this.editor.fire("contentDomUnload");delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var i=CKEDITOR.dom.walker.bogus(),k=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi, +o=CKEDITOR.dom.walker.whitespaces(true),s=CKEDITOR.dom.walker.bookmark(false,true);CKEDITOR.on("instanceLoaded",function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty(); +c.fire("lockSnapshot");a(b);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})});CKEDITOR.on("instanceCreated",function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);if(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents")){var d=CKEDITOR.tools.getNextId(),e=CKEDITOR.dom.element.createFromHtml(''+this.lang.common.editorHelp+ +"");c.append(e);a.changeAttr("aria-describedby",d)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");var r=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,d){var e,f,j,l,t=[],q=d.range.startContainer;e=d.range.startPath();for(var q=m[q.getName()],h=0,k=c.getChildren(),i=k.count(),u=-1,o=-1,n=0,s=e.contains(m.$list);h-1)t[u].firstNotAllowed=1;if(o>-1)t[o].lastNotAllowed=1;return t}function d(b,c){var e=[],f=b.getChildren(),j=f.count(),l,t=0,q=m[c],h=!b.is(m.$inline)|| +b.is("br");for(h&&e.push(" ");t 
      ",o.document);o.insertNode(x); +o.setStartAfter(x)}y=new CKEDITOR.dom.elementPath(o.startContainer);i.endPath=B=new CKEDITOR.dom.elementPath(o.endContainer);if(!o.collapsed){var t=B.block||B.blockLimit,N=o.getCommonAncestor();t&&(!t.equals(N)&&!t.contains(N)&&o.checkEndOfBlock())&&i.zombies.push(t);o.deleteContents()}for(;(w=a(o.startContainer)&&o.startContainer.getChild(o.startOffset-1))&&a(w)&&w.isBlockBoundary()&&y.contains(w);)o.moveToPosition(w,CKEDITOR.POSITION_BEFORE_END);f(o,i.blockLimit,y,B);if(x){o.setEndBefore(x);o.collapse(); +x.remove()}x=o.startPath();if(t=x.contains(e,false,1)){o.splitElement(t);i.inlineStylesRoot=t;i.inlineStylesPeak=x.lastElement}x=o.createBookmark();(t=x.startNode.getPrevious(c))&&a(t)&&e(t)&&r.push(t);(t=x.startNode.getNext(c))&&a(t)&&e(t)&&r.push(t);for(t=x.startNode;(t=t.getParent())&&e(t);)r.push(t);o.moveToBookmark(x);if(x=u){x=i.range;if(i.type=="text"&&i.inlineStylesRoot){w=i.inlineStylesPeak;o=w.getDocument().createText("{cke-peak}");for(r=i.inlineStylesRoot.getParent();!w.equals(r);){o=o.appendTo(w.clone()); +w=w.getParent()}u=o.getOuterHtml().split("{cke-peak}").join(u)}w=i.blockLimit.getName();if(/^\s+|\s+$/.test(u)&&"span"in CKEDITOR.dtd[w])var M=' ',u=M+u+M;u=i.editor.dataProcessor.toHtml(u,{context:null,fixForBody:false,dontFilter:i.dontFilter,filter:i.editor.activeFilter,enterMode:i.editor.activeEnterMode});w=x.document.createElement("body");w.setHtml(u);if(M){w.getFirst().remove();w.getLast().remove()}if((M=x.startPath().block)&&!(M.getChildCount()==1&&M.getBogus()))a:{var D; +if(w.getChildCount()==1&&a(D=w.getFirst())&&D.is(k)){M=D.getElementsByTag("*");x=0;for(r=M.count();x0;else{v=D.startPath();if(!B.isBlock&&i.editor.config.autoParagraph!==false&&(i.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&i.editor.editable().equals(v.blockLimit)&& +!v.block)&&(Q=i.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&i.editor.config.autoParagraph!==false?i.editor.activeEnterMode==CKEDITOR.ENTER_DIV?"div":"p":false)){Q=M.createElement(Q);Q.appendBogus();D.insertNode(Q);CKEDITOR.env.needsBrFiller&&(I=Q.getBogus())&&I.remove();D.moveToPosition(Q,CKEDITOR.POSITION_BEFORE_END)}if((v=D.startPath().block)&&!v.equals(E)){if(I=v.getBogus()){I.remove();w.push(v)}E=v}B.firstNotAllowed&&(o=1);if(o&&B.isElement){v=D.startContainer;for(K=null;v&&!m[v.getName()][B.name];){if(v.equals(u)){v= +null;break}K=v;v=v.getParent()}if(v){if(K){S=D.splitElement(K);i.zombies.push(S);i.zombies.push(K)}}else{K=u.getName();T=!x;v=x==y.length-1;K=d(B.node,K);for(var O=[],W=K.length,X=0,Z=void 0,$=0,aa=-1;X0;){d=a.getItem(b);if(!CKEDITOR.tools.trim(d.getHtml())){d.appendBogus();CKEDITOR.env.ie&&(CKEDITOR.env.version<9&&d.getChildCount())&&d.getFirst().remove()}}}return function(d){var e=d.startContainer,f=e.getAscendant("table",1),m=false;c(f.getElementsByTag("td"));c(f.getElementsByTag("th"));f=d.clone();f.setStart(e,0);f=a(f).lastBackward();if(!f){f=d.clone();f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);f=a(f).lastForward();m=true}f|| +(f=e);if(f.is("table")){d.setStartAt(f,CKEDITOR.POSITION_BEFORE_START);d.collapse(true);f.remove()}else{f.is({tbody:1,thead:1,tfoot:1})&&(f=b(f,"tr",m));f.is("tr")&&(f=b(f,f.getParent().is("thead")?"th":"td",m));(e=f.getBogus())&&e.remove();d.moveToPosition(f,m?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END)}}}()})(); +(function(){function a(){var a=this._.fakeSelection,b;if(a){b=this.getSelection(1);if(!b||!b.isHidden()){a.reset();a=0}}if(!a){a=b||this.getSelection(1);if(!a||a.getType()==CKEDITOR.SELECTION_NONE)return}this.fire("selectionCheck",a);b=this.elementPath();if(!b.compare(this._.selectionPreviousPath)){if(CKEDITOR.env.webkit)this._.previousActive=this.document.getActive();this._.selectionPreviousPath=b;this.fire("selectionChange",{selection:a,path:b})}}function e(){r=true;if(!s){b.call(this);s=CKEDITOR.tools.setTimeout(b, +200,this)}}function b(){s=null;if(r){CKEDITOR.tools.setTimeout(a,0,this);r=false}}function c(a){function b(c,d){return!c||c.type==CKEDITOR.NODE_TEXT?false:a.clone()["moveToElementEdit"+(d?"End":"Start")](c)}if(!(a.root instanceof CKEDITOR.editable))return false;var c=a.startContainer,d=a.getPreviousNode(v,null,c),e=a.getNextNode(v,null,c);return b(d)||b(e,1)||!d&&!e&&!(c.type==CKEDITOR.NODE_ELEMENT&&c.isBlockBoundary()&&c.getBogus())?true:false}function d(a){return a.getCustomData("cke-fillingChar")} +function f(a,b){var c=a&&a.removeCustomData("cke-fillingChar");if(c){if(b!==false){var d,e=a.getDocument().getSelection().getNative(),f=e&&e.type!="None"&&e.getRangeAt(0);if(c.getLength()>1&&f&&f.intersectsNode(c.$)){d=[e.anchorOffset,e.focusOffset];f=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0]--;f&&d[1]--;var g;f=e;if(!f.isCollapsed){g=f.getRangeAt(0);g.setStart(f.anchorNode,f.anchorOffset);g.setEnd(f.focusNode,f.focusOffset);g=g.collapsed}g&&d.unshift(d.pop())}}c.setText(h(c.getText())); +if(d){c=e.getRangeAt(0);c.setStart(c.startContainer,d[0]);c.setEnd(c.startContainer,d[1]);e.removeAllRanges();e.addRange(c)}}}function h(a){return a.replace(/\u200B( )?/g,function(a){return a[1]?" ":""})}function n(a,b,c){var d=a.on("focus",function(a){a.cancel()},null,null,-100);if(CKEDITOR.env.ie)var e=a.getDocument().on("selectionchange",function(a){a.cancel()},null,null,-100);else{var f=new CKEDITOR.dom.range(a);f.moveToElementEditStart(a);var g=a.getDocument().$.createRange();g.setStart(f.startContainer.$, +f.startOffset);g.collapse(1);b.removeAllRanges();b.addRange(g)}c&&a.focus();d.removeListener();e&&e.removeListener()}function i(a){var b=CKEDITOR.dom.element.createFromHtml('
       
      ',a.document);a.fire("lockSnapshot");a.editable().append(b);var c=a.getSelection(1),d=a.createRange(),e=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(b,CKEDITOR.POSITION_AFTER_START); +d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([d]);e.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=b}function k(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),f=e[0];if(e.length==1&&f.collapsed)if((d=f[d<38?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getAttribute("contenteditable")=="false"){a.getSelection().fake(d);c.data.preventDefault();c.cancel()}}}} +function o(a){for(var b=0;b=d.getLength()?h.setStartAfter(d):h.setStartBefore(d)); +e&&e.type==CKEDITOR.NODE_TEXT&&(g?h.setEndAfter(e):h.setEndBefore(e));d=new CKEDITOR.dom.walker(h);d.evaluator=function(d){if(d.type==CKEDITOR.NODE_ELEMENT&&d.isReadOnly()){var e=c.clone();c.setEndBefore(d);c.collapsed&&a.splice(b--,1);if(!(d.getPosition(h.endContainer)&CKEDITOR.POSITION_CONTAINS)){e.setStartAfter(d);e.collapsed||a.splice(b+1,0,e)}return true}return false};d.next()}}return a}var s,r,v=CKEDITOR.dom.walker.invisible(1),g=function(){function a(b){return function(a){var c=a.editor.createRange(); +c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return false}}function b(a){return function(b){var c=b.editor,d=c.createRange(),e;if(!(e=d.moveToClosestEditablePosition(b.selected,a)))e=d.moveToClosestEditablePosition(b.selected,!a);e&&c.getSelection().selectRanges([d]);c.fire("saveSnapshot");b.selected.remove();if(!e){d.moveToElementEditablePosition(c.editable());c.getSelection().selectRanges([d])}c.fire("saveSnapshot");return false}}var c=a(),d=a(1);return{37:c, +38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(b){function c(){var a=d.getSelection();a&&a.removeAllRanges()}var d=b.editor;d.on("contentDom",function(){var b=d.document,c=CKEDITOR.document,g=d.editable(),j=b.getBody(),l=b.getDocumentElement(),h=g.isInline(),i,o;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();if(i!==0)if((a=d.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$){a=d.createRange();a.moveToElementEditStart(g);a.select()}}, +null,null,-2);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){i&&CKEDITOR.env.webkit&&(i=d._.previousActive&&d._.previousActive.equals(b.getActive()));d.unlockSelection(i);i=0},null,null,-1);g.attachListener(g,"mousedown",function(){i=0});if(CKEDITOR.env.ie||h){var n=function(){o=new CKEDITOR.dom.selection(d.getSelection());o.lock()};p?g.attachListener(g,"beforedeactivate",n,null,null,-1):g.attachListener(d,"selectionCheck",n,null,null,-1);g.attachListener(g,CKEDITOR.env.webkit? +"DOMFocusOut":"blur",function(){d.lockSelection(o);i=1},null,null,-1);g.attachListener(g,"mousedown",function(){i=0})}if(CKEDITOR.env.ie&&!h){var t;g.attachListener(g,"mousedown",function(a){if(a.data.$.button==2){a=d.document.getSelection();if(!a||a.getType()==CKEDITOR.SELECTION_NONE)t=d.window.getScrollPosition()}});g.attachListener(g,"mouseup",function(a){if(a.data.$.button==2&&t){d.document.$.documentElement.scrollLeft=t.x;d.document.$.documentElement.scrollTop=t.y}t=null});if(b.$.compatMode!= +"BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)l.on("mousedown",function(a){function b(a){a=a.data.$;if(e){var c=j.$.createTextRange();try{c.moveToPoint(a.x,a.y)}catch(d){}e.setEndPoint(g.compareEndPoints("StartToStart",c)<0?"EndToEnd":"StartToStart",c);e.select()}}function d(){l.removeListener("mousemove",b);c.removeListener("mouseup",d);l.removeListener("mouseup",d);e.select()}a=a.data;if(a.getTarget().is("html")&&a.$.y7&&CKEDITOR.env.version<11){l.on("mousedown",function(a){if(a.data.getTarget().is("html")){c.on("mouseup",x);l.on("mouseup",x)}});var x=function(){c.removeListener("mouseup",x);l.removeListener("mouseup",x);var a=CKEDITOR.document.$.selection,d=a.createRange();a.type!="None"&&d.parentElement().ownerDocument==b.$&&d.select()}}}}g.attachListener(g,"selectionchange", +a,d);g.attachListener(g,"keyup",e,d);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){d.forceNextSelectionCheck();d.selectionChange(1)});if(h&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var y;g.attachListener(g,"mousedown",function(){y=1});g.attachListener(b.getDocumentElement(),"mouseup",function(){y&&e.call(d);y=0})}else g.attachListener(CKEDITOR.env.ie?g:b.getDocumentElement(),"mouseup",e,d);CKEDITOR.env.webkit&&g.attachListener(b,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:f(g)}}, +null,null,-1);g.attachListener(g,"keydown",k(d),null,null,-1)});d.on("setData",function(){d.unlockSelection();CKEDITOR.env.webkit&&c()});d.on("contentDomUnload",function(){d.unlockSelection()});if(CKEDITOR.env.ie9Compat)d.on("beforeDestroy",c,null,null,9);d.on("dataReady",function(){delete d._.fakeSelection;delete d._.hiddenSelectionContainer;d.selectionChange(1)});d.on("loadSnapshot",function(){var a=d.editable().getLast(function(a){return a.type==CKEDITOR.NODE_ELEMENT});a&&a.hasAttribute("data-cke-hidden-sel")&& +a.remove()},null,null,100);d.on("key",function(a){if(d.mode=="wysiwyg"){var b=d.getSelection();if(b.isFake){var c=g[a.data.keyCode];if(c)return c({editor:d,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});CKEDITOR.on("instanceReady",function(a){var b=a.editor;if(CKEDITOR.env.webkit){b.on("selectionChange",function(){var a=b.editable(),c=d(a);c&&(c.getCustomData("ready")?f(a):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){f(b.editable())},null,null,-1);var c, +e,a=function(){var a=b.editable();if(a)if(a=d(a)){var f=b.document.$.defaultView.getSelection();f.type=="Caret"&&f.anchorNode==a.$&&(e=1);c=a.getText();a.setText(h(c))}},g=function(){var a=b.editable();if(a)if(a=d(a)){a.setText(c);if(e){b.document.$.defaultView.getSelection().setPosition(a.$,a.getLength());e=0}}};b.on("beforeUndoImage",a);b.on("afterUndoImage",g);b.on("beforeGetData",a,null,null,0);b.on("getData",g)}});CKEDITOR.editor.prototype.selectionChange=function(b){(b?a:e).call(this)};CKEDITOR.editor.prototype.getSelection= +function(a){if((this._.savedSelection||this._.fakeSelection)&&!a)return this._.savedSelection||this._.fakeSelection;return(a=this.editable())&&this.mode=="wysiwyg"?new CKEDITOR.dom.selection(a):null};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);if(a.getType()!=CKEDITOR.SELECTION_NONE){!a.isLocked&&a.lock();this._.savedSelection=a;return true}return false};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;if(b){b.unlock(a);delete this._.savedSelection; +return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var p= +typeof window.getSelection!="function",z=1;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection)var b=a,a=a.root;var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:z++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=a=c?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b){CKEDITOR.tools.extend(this._.cache,b._.cache);this.isFake=b.isFake;this.isLocked=b.isLocked;return this}b=p?this.document.$.selection:this.document.getWindow().$.getSelection(); +if(CKEDITOR.env.webkit)(b.type=="None"&&this.document.getActive().equals(a)||b.type=="Caret"&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b);else if(CKEDITOR.env.gecko)b&&(this.document.getActive().equals(a)&&b.anchorNode&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b,true);else if(CKEDITOR.env.ie){var d;try{d=this.document.getActive()}catch(e){}if(p)b.type=="None"&&(d&&d.equals(this.document.getDocumentElement()))&&n(a,null,true);else{(b=b&&b.anchorNode)&&(b=new CKEDITOR.dom.node(b)); +d&&(d.equals(this.document.getDocumentElement())&&b&&(a.equals(b)||a.contains(b)))&&n(a,null,true)}}d=this.getNative();var f,g;if(d)if(d.getRangeAt)f=(g=d.rangeCount&&d.getRangeAt(0))&&new CKEDITOR.dom.node(g.commonAncestorContainer);else{try{g=d.createRange()}catch(h){}f=g&&CKEDITOR.dom.element.get(g.item&&g.item(0)||g.parentElement())}if(!f||!(f.type==CKEDITOR.NODE_ELEMENT||f.type==CKEDITOR.NODE_TEXT)||!this.root.equals(f)&&!this.root.contains(f)){this._.cache.type=CKEDITOR.SELECTION_NONE;this._.cache.startElement= +null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var A={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype={getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=p?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:p?function(){var a= +this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;if(d=="Text")b=CKEDITOR.SELECTION_TEXT;if(d=="Control")b=CKEDITOR.SELECTION_ELEMENT;if(c.createRange().parentElement())b=CKEDITOR.SELECTION_TEXT}catch(e){}return a.type=b}:function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(c.rangeCount==1){var c=c.getRangeAt(0),d=c.startContainer;if(d==c.endContainer&& +d.nodeType==1&&c.endOffset-c.startOffset==1&&A[d.childNodes[c.startOffset].nodeName.toLowerCase()])b=CKEDITOR.SELECTION_ELEMENT}return a.type=b},getRanges:function(){var a=p?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c);var d=b.parentElement();if(!d.hasChildNodes())return{container:d,offset:0};for(var e=d.children,f,g,h=b.duplicate(),i=0,l=e.length-1,t=-1,m,k;i<=l;){t=Math.floor((i+l)/2);f=e[t];h.moveToElementText(f);m=h.compareEndPoints("StartToStart", +b);if(m>0)l=t-1;else if(m<0)i=t+1;else return{container:d,offset:a(f)}}if(t==-1||t==e.length-1&&m<0){h.moveToElementText(d);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!h){f=e[e.length-1];return f.nodeType!=CKEDITOR.NODE_TEXT?{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}}for(d=e.length;h>0&&d>0;){g=e[--d];if(g.nodeType==CKEDITOR.NODE_TEXT){k=g;h=h-g.nodeValue.length}}return{container:k,offset:-h}}h.collapse(m>0?true:false);h.setEndPoint(m> +0?"StartToStart":"EndToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;if(!h)return{container:d,offset:a(f)+(m>0?0:1)};for(;h>0;)try{g=f[m>0?"previousSibling":"nextSibling"];if(g.nodeType==CKEDITOR.NODE_TEXT){h=h-g.nodeValue.length;k=g}f=g}catch(o){return{container:d,offset:a(f)}}return{container:k,offset:m>0?-h:k.nodeValue.length+h}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root); +d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d==CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e1){h=a[a.length- +1];a[0].setEnd(h.endContainer,h.endOffset)}h=a[0];var a=h.collapsed,o,n,r;if((d=h.getEnclosedNode())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getName()in A&&(!d.is("a")||!d.getText()))try{r=d.$.createControlRange();r.addElement(d.$);r.select();return}catch(s){}(h.startContainer.type==CKEDITOR.NODE_ELEMENT&&h.startContainer.getName()in b||h.endContainer.type==CKEDITOR.NODE_ELEMENT&&h.endContainer.getName()in b)&&h.shrink(CKEDITOR.NODE_ELEMENT,true);r=h.createBookmark();b=r.startNode;if(!a)g=r.endNode;r=h.document.$.body.createTextRange(); +r.moveToElementText(b.$);r.moveStart("character",1);if(g){i=h.document.$.body.createTextRange();i.moveToElementText(g.$);r.setEndPoint("EndToEnd",i);r.moveEnd("character",-1)}else{o=b.getNext(k);n=b.hasAscendant("pre");o=!(o&&o.getText&&o.getText().match(i))&&(n||!b.hasPrevious()||b.getPrevious().is&&b.getPrevious().is("br"));n=h.document.createElement("span");n.setHtml("");n.insertBefore(b);o&&h.document.createText("").insertBefore(b)}h.setStartBefore(b);b.remove();if(a){if(o){r.moveStart("character", +-1);r.select();h.document.$.selection.clear()}else r.select();h.moveToPosition(n,CKEDITOR.POSITION_BEFORE_START);n.remove()}else{h.setEndBefore(g);g.remove();r.select()}}else{g=this.getNative();if(!g)return;this.removeAllRanges();for(r=0;r=0){h.collapse(1);n.setEnd(h.endContainer.$,h.endOffset)}else throw t;}g.addRange(n)}}this.reset();this.root.fire("selectionchange")}}},fake:function(a){var b=this.root.editor;this.reset();i(b);var c=this._.cache,d=new CKEDITOR.dom.range(this.root);d.setStartBefore(a);d.setEndAfter(a);c.ranges=new CKEDITOR.dom.rangeList(d);c.selectedElement=c.startElement=a;c.type=CKEDITOR.SELECTION_ELEMENT; +c.selectedText=c.nativeSel=null;this.isFake=1;this.rev=z++;b._.fakeSelection=this;this.root.fire("selectionchange")},isHidden:function(){var a=this.getCommonAncestor();a&&a.type==CKEDITOR.NODE_TEXT&&(a=a.getParent());return!(!a||!a.data("cke-hidden-sel"))},createBookmarks:function(a){a=this.getRanges().createBookmarks(a);this.isFake&&(a.isFake=1);return a},createBookmarks2:function(a){a=this.getRanges().createBookmarks2(a);this.isFake&&(a.isFake=1);return a},selectBookmarks:function(a){for(var b= +[],c=0;c]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g, +" ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var g=a.getDocument().createElement("div");g.append(e);e.$.outerHTML="
      "+f+"
      ";e.copyAttributes(g.getFirst());e=g.getFirst().remove()}else e.setHtml(f);b=e}else f?b=s(c?[a.getHtml()]:k(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,h;if((h=c.getPrevious(C))&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("pre")){d=o(h.getHtml(),/\n$/,"")+"\n\n"+o(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="
      "+d+"
      ":c.setHtml(d);h.remove()}}else c&& +p(b)}function k(a){a.getName();var b=[];o(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+"
      "+c+"
      "}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function o(a,b,c){var d="",e="",a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function s(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));
      +for(var d=0;d"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat(" ",a.length-1)+" "});if(c){var f=b.clone();f.setHtml(e);c.append(f)}else b.setHtml(e)}return c||b}function r(a,b){var c=this._.definition,
      +d=c.attributes,c=c.styles,e=j(this)[a.getName()],f=CKEDITOR.tools.isEmpty(d)&&CKEDITOR.tools.isEmpty(c),h;for(h in d)if(!((h=="class"||this._.definition.fullMatch)&&a.getAttribute(h)!=m(h,d[h]))&&!(b&&h.slice(0,5)=="data-")){f=a.hasAttribute(h);a.removeAttribute(h)}for(var i in c)if(!(this._.definition.fullMatch&&a.getStyle(i)!=m(i,c[i],true))){f=f||!!a.getStyle(i);a.removeStyle(i)}g(a,e,P[a.getName()]);f&&(this._.definition.alwaysRemoveElement?p(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==
      +CKEDITOR.ENTER_BR&&!a.hasAttributes()?p(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function v(a){for(var b=j(this),c=a.getElementsByTag(this.element),d,e=c.count();--e>=0;){d=c.getItem(e);d.isReadOnly()||r.call(this,d,true)}for(var f in b)if(f!=this.element){c=a.getElementsByTag(f);for(e=c.count()-1;e>=0;e--){d=c.getItem(e);d.isReadOnly()||g(d,b[f])}}}function g(a,b,c){if(b=b&&b.attributes)for(var d=0;d",a||b.name,"");return c.join("")},getDefinition:function(){return this._.definition}};CKEDITOR.style.getStyleText=function(a){var b=a._ST;if(b)return b;var b=a.styles,c=a.attributes&&a.attributes.style||"",
      +d="";c.length&&(c=c.replace(L,";"));for(var e in b){var f=b[e],g=(e+":"+f).replace(L,";");f=="inherit"?d=d+g:c=c+g}c.length&&(c=CKEDITOR.tools.normalizeCssText(c,true));return a._ST=c+d};CKEDITOR.style.customHandlers={};CKEDITOR.style.addCustomHandler=function(a){var b=function(a){this._={definition:a};this.setup&&this.setup(a)};b.prototype=CKEDITOR.tools.extend(CKEDITOR.tools.prototypedCopy(CKEDITOR.style.prototype),{assignedTo:CKEDITOR.STYLE_OBJECT},a,true);return this.customHandlers[a.type]=b};
      +var J=CKEDITOR.POSITION_PRECEDING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED,F=CKEDITOR.POSITION_FOLLOWING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED})();CKEDITOR.styleCommand=function(a,e){this.requiredContent=this.allowedContent=this.style=a;CKEDITOR.tools.extend(this,e,true)};CKEDITOR.styleCommand.prototype.exec=function(a){a.focus();this.state==CKEDITOR.TRISTATE_OFF?a.applyStyle(this.style):this.state==CKEDITOR.TRISTATE_ON&&a.removeStyle(this.style)};
      +CKEDITOR.stylesSet=new CKEDITOR.resourceManager("","stylesSet");CKEDITOR.addStylesSet=CKEDITOR.tools.bind(CKEDITOR.stylesSet.add,CKEDITOR.stylesSet);CKEDITOR.loadStylesSet=function(a,e,b){CKEDITOR.stylesSet.addExternal(a,e,"");CKEDITOR.stylesSet.load(a,b)};
      +CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{attachStyleStateChange:function(a,e){var b=this._.styleStateChangeCallbacks;if(!b){b=this._.styleStateChangeCallbacks=[];this.on("selectionChange",function(a){for(var d=0;d"}});"use strict";
      +(function(){var a={},e={},b;for(b in CKEDITOR.dtd.$blockLimit)b in CKEDITOR.dtd.$list||(a[b]=1);for(b in CKEDITOR.dtd.$block)b in CKEDITOR.dtd.$blockLimit||b in CKEDITOR.dtd.$empty||(e[b]=1);CKEDITOR.dom.elementPath=function(b,d){var f=null,h=null,n=[],i=b,k,d=d||b.getDocument().getBody();do if(i.type==CKEDITOR.NODE_ELEMENT){n.push(i);if(!this.lastElement){this.lastElement=i;if(i.is(CKEDITOR.dtd.$object)||i.getAttribute("contenteditable")=="false")continue}if(i.equals(d))break;if(!h){k=i.getName();
      +i.getAttribute("contenteditable")=="true"?h=i:!f&&e[k]&&(f=i);if(a[k]){var o;if(o=!f){if(k=k=="div"){a:{k=i.getChildren();o=0;for(var s=k.count();o-1}:typeof a=="function"?c=a:typeof a=="object"&&(c=
      +function(b){return b.getName()in a});var d=this.elements,f=d.length;e&&f--;if(b){d=Array.prototype.slice.call(d,0);d.reverse()}for(e=0;e=c){f=d.createText("");f.insertAfter(this)}else{a=d.createText("");a.insertAfter(f);a.remove()}return f},substring:function(a,
      +e){return typeof e!="number"?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,e)}});
      +(function(){function a(a,c,d){var e=a.serializable,h=c[d?"endContainer":"startContainer"],n=d?"endOffset":"startOffset",i=e?c.document.getById(a.startNode):a.startNode,a=e?c.document.getById(a.endNode):a.endNode;if(h.equals(i.getPrevious())){c.startOffset=c.startOffset-h.getLength()-a.getPrevious().getLength();h=a.getNext()}else if(h.equals(a.getPrevious())){c.startOffset=c.startOffset-h.getLength();h=a.getNext()}h.equals(i.getParent())&&c[n]++;h.equals(a.getParent())&&c[n]++;c[d?"endContainer":"startContainer"]=
      +h;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,e)};var e={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),d=[],e;return{getNextRange:function(h){e=e==void 0?0:e+1;var n=a[e];if(n&&a.length>1){if(!e)for(var i=a.length-1;i>=0;i--)d.unshift(a[i].createBookmark(true));if(h)for(var k=0;a[e+k+1];){for(var o=n.document,h=0,i=o.getById(d[k].endNode),o=o.getById(d[k+
      +1].startNode);;){i=i.getNextSourceNode(false);if(o.equals(i))h=1;else if(c(i)||i.type==CKEDITOR.NODE_ELEMENT&&i.isBlockBoundary())continue;break}if(!h)break;k++}for(n.moveToBookmark(d.shift());k--;){i=a[++e];i.moveToBookmark(d.shift());n.setEnd(i.endContainer,i.endOffset)}}return n}}},createBookmarks:function(b){for(var c=[],d,e=0;eb?-1:1}),e=0,f;e
    ',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var e=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!!(e&&e==b)}catch(c){CKEDITOR.env.hc=false}a.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc"; +CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending){delete CKEDITOR._.pending;for(e=0;ec;c++){var f=a,h=c,d;d=parseInt(a[c],16);d=("0"+(0>e?0|d*(1+e):0|d+(255-d)*e).toString(16)).slice(-2);f[h]=d}return"#"+a.join("")}}(),c=function(){var b=new CKEDITOR.template("background:#{to};background-image:-webkit-gradient(linear,lefttop,leftbottom,from({from}),to({to}));background-image:-moz-linear-gradient(top,{from},{to});background-image:-webkit-linear-gradient(top,{from},{to});background-image:-o-linear-gradient(top,{from},{to});background-image:-ms-linear-gradient(top,{from},{to});background-image:linear-gradient(top,{from},{to});filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='{from}',endColorstr='{to}');");return function(c, +a){return b.output({from:c,to:a})}}(),f={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ {defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_bottom [{defaultGradient}border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [{defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [{defaultGradient}outline-color:{defaultBorder};border-top-color:{defaultBorder};] {id} .cke_dialog_tab [{lightGradient}border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [{mediumGradient}] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} .cke_toolgroup [{lightGradient}border-color:{defaultBorder};] {id} a.cke_button_off:hover, {id} a.cke_button_off:focus, {id} a.cke_button_off:active [{mediumGradient}] {id} .cke_button_on [{ckeButtonOn}] {id} .cke_toolbar_separator [background-color: {ckeToolbarSeparator};] {id} .cke_combo_button [border-color:{defaultBorder};{lightGradient}] {id} a.cke_combo_button:hover, {id} a.cke_combo_button:focus, {id} .cke_combo_on a.cke_combo_button [border-color:{defaultBorder};{mediumGradient}] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover, {id} a.cke_path_item:focus, {id} a.cke_path_item:active [background-color:{elementsPathBg};] {id}.cke_panel [border-color:{defaultBorder};] "), +panel:new CKEDITOR.template(".cke_panel_grouptitle [{lightGradient}border-color:{defaultBorder};] .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menubutton:hover .cke_menubutton_icon, .cke_menubutton:focus .cke_menubutton_icon, .cke_menubutton:active .cke_menubutton_icon [background-color:{menubuttonIconHover};] .cke_menuseparator [background-color:{menubuttonIcon};] a:hover.cke_colorbox, a:focus.cke_colorbox, a:active.cke_colorbox [border-color:{defaultBorder};] a:hover.cke_colorauto, a:hover.cke_colormore, a:focus.cke_colorauto, a:focus.cke_colormore, a:active.cke_colorauto, a:active.cke_colormore [background-color:{ckeColorauto};border-color:{defaultBorder};] ")}; +return function(g,e){var a=g.uiColor,a={id:"."+g.id,defaultBorder:b(a,-0.1),defaultGradient:c(b(a,0.9),a),lightGradient:c(b(a,1),b(a,0.7)),mediumGradient:c(b(a,0.8),b(a,0.5)),ckeButtonOn:c(b(a,0.6),b(a,0.7)),ckeResizer:b(a,-0.4),ckeToolbarSeparator:b(a,0.5),ckeColorauto:b(a,0.8),dialogBody:b(a,0.7),dialogTabSelected:c("#FFFFFF","#FFFFFF"),dialogTabSelectedBorder:"#FFF",elementsPathColor:b(a,-0.6),elementsPathBg:a,menubuttonIcon:b(a,0.5),menubuttonIconHover:b(a,0.3)};return f[e].output(a).replace(/\[/g, +"{").replace(/\]/g,"}")}}();CKEDITOR.plugins.add("dialogui",{onLoad:function(){var i=function(b){this._||(this._={});this._["default"]=this._.initValue=b["default"]||"";this._.required=b.required||!1;for(var a=[this._],d=1;darguments.length)){var c=i.call(this,a);c.labelId=CKEDITOR.tools.getNextId()+"_label";this._.children=[];CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"div",null,{role:"presentation"},function(){var f=[],d=a.required?" cke_required":"";"horizontal"!= +a.labelLayout?f.push('",'
    ',e.call(this,b,a),"
    "):(d={type:"hbox",widths:a.widths,padding:0,children:[{type:"html",html:'