Skip to content
Browse files

added sources

  • Loading branch information...
1 parent 747ee68 commit 6155e72f90c864c32300a569b7e2edf5026e608b @antonCPU committed Oct 7, 2012
Showing with 6,252 additions and 0 deletions.
  1. +89 −0 AppManagerModule.php
  2. BIN assets/css/bg.gif
  3. +166 −0 assets/css/form.css
  4. +36 −0 assets/css/ie.css
  5. +245 −0 assets/css/main.css
  6. +29 −0 assets/css/print.css
  7. +238 −0 assets/css/screen.css
  8. +267 −0 assets/js/jquery.textarea.js.js
  9. +65 −0 components/AppManagerConfig.php
  10. +277 −0 components/AppManagerController.php
  11. +756 −0 components/AppManagerEntity.php
  12. +109 −0 components/AppManagerFlash.php
  13. +66 −0 components/AppManagerModel.php
  14. +54 −0 components/AppManagerNode.php
  15. +79 −0 components/AppManagerPhpValidator.php
  16. +228 −0 components/parser/AppManagerParser.php
  17. +161 −0 components/parser/AppManagerProperty.php
  18. +110 −0 components/writer/AppManagerWriter.php
  19. +3 −0 components/writer/templates/config.php
  20. +8 −0 components/writer/templates/header.php
  21. +13 −0 components/writer/templates/options.php
  22. +9 −0 controllers/ComponentController.php
  23. +20 −0 controllers/ErrorController.php
  24. +9 −0 controllers/ExtensionController.php
  25. +9 −0 controllers/ModuleController.php
  26. +37 −0 messages/ru/core.php
  27. +65 −0 models/AppManagerComponent.php
  28. +23 −0 models/AppManagerExtension.php
  29. +65 −0 models/AppManagerModuleEntity.php
  30. +207 −0 models/AppManagerOption.php
  31. +210 −0 models/AppManagerOptions.php
  32. +96 −0 vendors/Zend/Exception.php
  33. +329 −0 vendors/Zend/Loader.php
  34. +247 −0 vendors/Zend/Reflection/Class.php
  35. +294 −0 vendors/Zend/Reflection/Docblock.php
  36. +145 −0 vendors/Zend/Reflection/Docblock/Tag.php
  37. +93 −0 vendors/Zend/Reflection/Docblock/Tag/Param.php
  38. +72 −0 vendors/Zend/Reflection/Docblock/Tag/Return.php
  39. +36 −0 vendors/Zend/Reflection/Exception.php
  40. +85 −0 vendors/Zend/Reflection/Extension.php
  41. +412 −0 vendors/Zend/Reflection/File.php
  42. +129 −0 vendors/Zend/Reflection/Function.php
  43. +168 −0 vendors/Zend/Reflection/Method.php
  44. +123 −0 vendors/Zend/Reflection/Parameter.php
  45. +68 −0 vendors/Zend/Reflection/Property.php
  46. +53 −0 views/default/index.php
  47. +76 −0 views/default/update.php
  48. +90 −0 views/default/view.php
  49. +11 −0 views/error/show.php
  50. +72 −0 views/layouts/main.php
View
89 AppManagerModule.php
@@ -0,0 +1,89 @@
+<?php
+
+class AppManagerModule extends CWebModule
+{
+ public $defaultController = 'module';
+ public $config = 'config/main.php';
+ public $layout = '/layouts/main';
+ public $errorAction = '/appManager/error/show';
+
+ protected static $settings;
+ private $_assetsUrl;
+
+ public function init()
+ {
+ //custom error action
+ Yii::app()->getErrorHandler()->errorAction = $this->errorAction;
+
+ // import the module-level models and components
+ $this->setImport(array(
+ 'appManager.models.*',
+ 'appManager.components.*',
+ 'appManager.components.parser.*',
+ ));
+ }
+
+ public function getName()
+ {
+ return self::t('App Manager');
+ }
+
+ public function beforeControllerAction($controller, $action)
+ {
+ if(parent::beforeControllerAction($controller, $action)) {
+ // this method is called before any module controller action is performed
+ // you may place customized code here
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static function config($path = null)
+ {
+ if (null === self::$settings) {
+ $config = self::getInstance()->getConfigLocation();
+ self::$settings = new AppManagerConfig($config);
+ }
+ return self::$settings->itemAtPath($path);
+ }
+
+ public static function getInstance()
+ {
+ return Yii::app()->controller->module;
+ }
+
+ public static function t($message, $params = array())
+ {
+ return Yii::t('AppManagerModule.core', $message, $params);
+ }
+
+ public function getConfigLocation()
+ {
+ return Yii::app()->basePath . DIRECTORY_SEPARATOR . $this->config;
+ }
+
+ public function getAssetsUrl()
+ {
+ if (null === $this->_assetsUrl) {
+ $this->_assetsUrl = Yii::app()->getAssetManager()
+ ->publish(Yii::getPathOfAlias('appManager.assets'));
+ }
+ return $this->_assetsUrl;
+ }
+
+ public function getCssUrl($name)
+ {
+ return $this->getAssetsUrl() . '/css/' . $name . '.css';
+ }
+
+ public function getJsUrl($name)
+ {
+ return $this->getAssetsUrl() . '/js/' . $name . '.js';
+ }
+
+ public function registerJs($name)
+ {
+ Yii::app()->clientScript->registerScriptFile($this->getJsUrl($name));
+ }
+}
View
BIN assets/css/bg.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
166 assets/css/form.css
@@ -0,0 +1,166 @@
+/**
+ * CSS styles for forms generated by yiic.
+ *
+ * The styles can be applied to the following form structure:
+ *
+ * <div class="form">
+ * <div class="row">
+ * <label for="inputid">xyz</label>
+ * <input name="inputid" id="inputid" type="text" />
+ * <p class="hint">hint text</p>
+ * </div>
+ * <div class="row">
+ * <label for="inputid">xyz</label>
+ * <input name="inputid" id="inputid" type="text" />
+ * <p class="hint">hint text</p>
+ * </div>
+ * <div class="row buttons">
+ * <label for="inputid">xyz</label>
+ * <input name="inputid" id="inputid" type="text" />
+ * <p class="hint">hint text</p>
+ * </div>
+ * </div>
+ *
+ * The above code will render the labels and input fields in separate lines.
+ * In order to render them in the same line, please use the "wide" form as follows,
+ *
+ * <div class="wide form">
+ * ......
+ * </div>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2010 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+div.form
+{
+}
+
+div.form input,
+div.form textarea,
+div.form select
+{
+ margin: 0.2em 0 0.5em 0;
+}
+
+div.form fieldset
+{
+ border: 1px solid #DDD;
+ padding: 10px;
+ margin: 0 0 10px 0;
+ -moz-border-radius:7px;
+}
+
+div.form label
+{
+ font-weight: bold;
+ font-size: 0.9em;
+ display: block;
+}
+
+div.form .row
+{
+ margin: 5px 0;
+}
+
+div.form .hint
+{
+ margin: 0;
+ padding: 0;
+ color: #999;
+}
+
+div.form .note
+{
+ font-style: italic;
+}
+
+div.form span.required
+{
+ color: red;
+}
+
+div.form div.error label,
+div.form label.error,
+div.form span.error
+{
+ color: #C00;
+}
+
+div.form div.error input,
+div.form div.error textarea,
+div.form div.error select,
+div.form input.error,
+div.form textarea.error,
+div.form select.error
+{
+ background: #FEE;
+ border-color: #C00;
+}
+
+div.form div.success input,
+div.form div.success textarea,
+div.form div.success select,
+div.form input.success,
+div.form textarea.success,
+div.form select.success
+{
+ background: #E6EFC2;
+ border-color: #C6D880;
+}
+
+
+div.form .errorSummary
+{
+ border: 2px solid #C00;
+ padding: 7px 7px 12px 7px;
+ margin: 0 0 20px 0;
+ background: #FEE;
+ font-size: 0.9em;
+}
+
+div.form .errorMessage
+{
+ color: red;
+ font-size: 0.9em;
+}
+
+div.form .errorSummary p
+{
+ margin: 0;
+ padding: 5px;
+}
+
+div.form .errorSummary ul
+{
+ margin: 0;
+ padding: 0 0 0 20px;
+}
+
+div.wide.form label
+{
+ float: left;
+ margin-right: 10px;
+ position: relative;
+ text-align: right;
+ width: 100px;
+}
+
+div.wide.form .row
+{
+ clear: left;
+}
+
+div.wide.form .buttons, div.wide.form .hint, div.wide.form .errorMessage
+{
+ clear: left;
+ padding-left: 110px;
+}
+
+div.form .block {width: 49%; float: left;}
+div.form .block.first {padding-right: 5px;}
+div.form .block.last {padding-left: 5px; border-left: 1px solid #DDDDDD;}
+div.form .textfield {width: 98%;}
+div.form textarea {width: 98%; height: 80px;}
View
36 assets/css/ie.css
@@ -0,0 +1,36 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* ie.css */
+body {text-align:center;}
+.container {text-align:left;}
+* html .column, * html .span-1, * html .span-2, * html .span-3, * html .span-4, * html .span-5, * html .span-6, * html .span-7, * html .span-8, * html .span-9, * html .span-10, * html .span-11, * html .span-12, * html .span-13, * html .span-14, * html .span-15, * html .span-16, * html .span-17, * html .span-18, * html .span-19, * html .span-20, * html .span-21, * html .span-22, * html .span-23, * html .span-24 {display:inline;overflow-x:hidden;}
+* html legend {margin:0px -8px 16px 0;padding:0;}
+sup {vertical-align:text-top;}
+sub {vertical-align:text-bottom;}
+html>body p code {*white-space:normal;}
+hr {margin:-8px auto 11px;}
+img {-ms-interpolation-mode:bicubic;}
+.clearfix, .container {display:inline-block;}
+* html .clearfix, * html .container {height:1%;}
+fieldset {padding-top:0;}
+legend {margin-top:-0.2em;margin-bottom:1em;margin-left:-0.5em;}
+textarea {overflow:auto;}
+label {vertical-align:middle;position:relative;top:-0.25em;}
+input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
+input.text:focus, input.title:focus {border-color:#666;}
+input.text, input.title, textarea, select {margin:0.5em 0;}
+input.checkbox, input.radio {position:relative;top:.25em;}
+form.inline div, form.inline p {vertical-align:middle;}
+form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;}
+button, input.button {position:relative;top:0.25em;}
View
245 assets/css/main.css
@@ -0,0 +1,245 @@
+body
+{
+ margin: 0;
+ padding: 0;
+ color: #555;
+ font: normal 10pt Arial,Helvetica,sans-serif;
+ background: #EFEFEF;
+}
+
+#page
+{
+ margin-top: 5px;
+ margin-bottom: 5px;
+ background: white;
+ border: 1px solid #C9E0ED;
+}
+
+#header
+{
+ margin: 0;
+ padding: 0;
+ border-top: 3px solid #C9E0ED;
+}
+
+#content
+{
+ padding: 20px;
+}
+
+#sidebar
+{
+ padding: 20px 20px 20px 0;
+}
+
+#footer
+{
+ padding: 10px;
+ margin: 10px 20px;
+ font-size: 0.8em;
+ text-align: center;
+ border-top: 1px solid #C9E0ED;
+}
+
+#logo
+{
+ padding: 10px 20px;
+ font-size: 200%;
+}
+
+#mainmenu
+{
+ background:white url(bg.gif) repeat-x left top;
+}
+
+#mainmenu ul
+{
+ padding:6px 20px 5px 20px;
+ margin:0px;
+}
+
+#mainmenu ul li
+{
+ display: inline;
+}
+
+#mainmenu ul li a
+{
+ color:#ffffff;
+ background-color:transparent;
+ font-size:12px;
+ font-weight:bold;
+ text-decoration:none;
+ padding:5px 8px;
+}
+
+#mainmenu ul li a:hover, #mainmenu ul li.active a
+{
+ color: #6399cd;
+ background-color:#EFF4FA;
+ text-decoration:none;
+}
+
+div.flash-error, div.flash-notice, div.flash-success
+{
+ padding:.8em;
+ margin-bottom:1em;
+ border:2px solid #ddd;
+}
+
+div.flash-error
+{
+ background:#FBE3E4;
+ color:#8a1f11;
+ border-color:#FBC2C4;
+}
+
+div.flash-notice
+{
+ background:#FFF6BF;
+ color:#514721;
+ border-color:#FFD324;
+}
+
+div.flash-success
+{
+ background:#E6EFC2;
+ color:#264409;
+ border-color:#C6D880;
+}
+
+div.flash-error a
+{
+ color:#8a1f11;
+}
+
+div.flash-notice a
+{
+ color:#514721;
+}
+
+div.flash-success a
+{
+ color:#264409;
+}
+
+div.form .rememberMe label
+{
+ display: inline;
+}
+
+div.view
+{
+ padding: 10px;
+ margin: 10px 0;
+ border: 1px solid #C9E0ED;
+}
+
+div.breadcrumbs
+{
+ font-size: 0.9em;
+ padding: 5px 20px;
+}
+
+div.breadcrumbs span
+{
+ font-weight: bold;
+}
+
+div.search-form
+{
+ padding: 10px;
+ margin: 10px 0;
+ background: #eee;
+}
+
+.portlet
+{
+
+}
+
+.portlet-decoration
+{
+ padding: 3px 8px;
+ background: #B7D6E7;
+ border-left: 5px solid #6FACCF;
+}
+
+.portlet-title
+{
+ font-size: 12px;
+ font-weight: bold;
+ padding: 0;
+ margin: 0;
+ color: #298dcd;
+}
+
+.portlet-content
+{
+ font-size:0.9em;
+ margin: 0 0 15px 0;
+ padding: 5px 8px;
+ background:#EFFDFF;
+}
+
+.portlet-content ul
+{
+ list-style-image:none;
+ list-style-position:outside;
+ list-style-type:none;
+ margin: 0;
+ padding: 0;
+}
+
+.portlet-content li
+{
+ padding: 2px 0 4px 0px;
+}
+
+.operations
+{
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.operations li
+{
+ padding-bottom: 2px;
+}
+
+.operations li a
+{
+ font: bold 12px Arial;
+ color: #0066A4;
+ display: block;
+ padding: 2px 0 2px 8px;
+ line-height: 15px;
+ text-decoration: none;
+}
+
+.operations li a:visited
+{
+ color: #0066A4;
+}
+
+.operations li a:hover
+{
+ background: #80CFFF;
+}
+
+.ui-widget-header {background: url("bg.gif") repeat-x scroll left top white !important;}
+.ui-widget-header .ui-state-default {background: none repeat scroll 0 0 #E5F1F4 !important;}
+.ui-widget-header .ui-state-active {background: none repeat scroll 0 0 #ffffff !important; }
+
+.grid-view h3 {float: left; margin-bottom: 0;}
+.form .note pre {display: inline; font: inherit; margin: 0;}
+code {white-space: pre;}
+.code {white-space: pre;}
+.view-title {height: 2.3em;}
+.view-title h1 {float: left; margin: 0 10px;}
+.view-title ul {list-style-type: none; line-height: 2;}
+.view-title li {float: left; margin-right: 5px;}
+.form legend {font-size: 1.4em; padding: 0 2px;}
+
+.form .note span {color: #ff0000;}
View
29 assets/css/print.css
@@ -0,0 +1,29 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* print.css */
+body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;}
+.container {background:none;}
+hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;}
+code {font:.9em "Courier New", Monaco, Courier, monospace;}
+a img {border:none;}
+p img.top {margin-top:0;}
+blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;}
+.small {font-size:.9em;}
+.large {font-size:1.1em;}
+.quiet {color:#999;}
+.hide {display:none;}
+a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;}
+a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;}
View
238 assets/css/screen.css
@@ -0,0 +1,238 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* reset.css */
+html {margin:0;padding:0;border:0;}
+body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}
+article, aside, details, figcaption, figure, dialog, footer, header, hgroup, menu, nav, section {display:block;}
+body {line-height:1.5;background:white;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;float:none !important;}
+table, th, td {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:'';}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+:focus {outline:0;}
+
+/* typography.css */
+html {font-size:100.01%;}
+body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
+h1 {font-size:2em;line-height:1;margin-bottom:0.5em;}
+h2 {font-size:1.6em;margin-bottom:0.75em;}
+h3 {font-size:1.4em;line-height:1;margin-bottom:1em;}
+h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
+h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+p {margin:0 0 1.5em;}
+.left {float:left !important;}
+p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
+.right {float:right !important;}
+p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
+a:focus, a:hover {color:#09f;}
+a {color:#06c;text-decoration:underline;}
+blockquote {margin:1.5em;color:#666;font-style:italic;}
+strong, dfn {font-weight:bold;}
+em, dfn {font-style:italic;}
+sup, sub {line-height:0;}
+abbr, acronym {border-bottom:1px dotted #666;}
+address {margin:0 0 1.5em;font-style:italic;}
+del {color:#666;}
+pre {margin:1.5em 0;white-space:pre;}
+pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+li ul, li ol {margin:0;}
+ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
+ul {list-style-type:disc;}
+ol {list-style-type:decimal;}
+dl {margin:0 0 1.5em 0;}
+dl dt {font-weight:bold;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;width:100%;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+tfoot {font-style:italic;}
+caption {background:#eee;}
+.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
+.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
+.hide {display:none;}
+.quiet {color:#666;}
+.loud {color:#000;}
+.highlight {background:#ff0;}
+.added {background:#060;color:#fff;}
+.removed {background:#900;color:#fff;}
+.first {margin-left:0;padding-left:0;}
+.last {margin-right:0;padding-right:0;}
+.top {margin-top:0;padding-top:0;}
+.bottom {margin-bottom:0;padding-bottom:0;}
+
+/* grid.css */
+.container {width:950px;margin:0 auto;}
+.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;}
+.last {margin-right:0;}
+.span-1 {width:30px;}
+.span-2 {width:70px;}
+.span-3 {width:110px;}
+.span-4 {width:150px;}
+.span-5 {width:190px;}
+.span-6 {width:230px;}
+.span-7 {width:270px;}
+.span-8 {width:310px;}
+.span-9 {width:350px;}
+.span-10 {width:390px;}
+.span-11 {width:430px;}
+.span-12 {width:470px;}
+.span-13 {width:510px;}
+.span-14 {width:550px;}
+.span-15 {width:590px;}
+.span-16 {width:630px;}
+.span-17 {width:670px;}
+.span-18 {width:710px;}
+.span-19 {width:750px;}
+.span-20 {width:790px;}
+.span-21 {width:830px;}
+.span-22 {width:870px;}
+.span-23 {width:910px;}
+.span-24 {width:950px;margin-right:0;}
+input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;}
+input.span-1, textarea.span-1 {width:18px;}
+input.span-2, textarea.span-2 {width:58px;}
+input.span-3, textarea.span-3 {width:98px;}
+input.span-4, textarea.span-4 {width:138px;}
+input.span-5, textarea.span-5 {width:178px;}
+input.span-6, textarea.span-6 {width:218px;}
+input.span-7, textarea.span-7 {width:258px;}
+input.span-8, textarea.span-8 {width:298px;}
+input.span-9, textarea.span-9 {width:338px;}
+input.span-10, textarea.span-10 {width:378px;}
+input.span-11, textarea.span-11 {width:418px;}
+input.span-12, textarea.span-12 {width:458px;}
+input.span-13, textarea.span-13 {width:498px;}
+input.span-14, textarea.span-14 {width:538px;}
+input.span-15, textarea.span-15 {width:578px;}
+input.span-16, textarea.span-16 {width:618px;}
+input.span-17, textarea.span-17 {width:658px;}
+input.span-18, textarea.span-18 {width:698px;}
+input.span-19, textarea.span-19 {width:738px;}
+input.span-20, textarea.span-20 {width:778px;}
+input.span-21, textarea.span-21 {width:818px;}
+input.span-22, textarea.span-22 {width:858px;}
+input.span-23, textarea.span-23 {width:898px;}
+input.span-24, textarea.span-24 {width:938px;}
+.append-1 {padding-right:40px;}
+.append-2 {padding-right:80px;}
+.append-3 {padding-right:120px;}
+.append-4 {padding-right:160px;}
+.append-5 {padding-right:200px;}
+.append-6 {padding-right:240px;}
+.append-7 {padding-right:280px;}
+.append-8 {padding-right:320px;}
+.append-9 {padding-right:360px;}
+.append-10 {padding-right:400px;}
+.append-11 {padding-right:440px;}
+.append-12 {padding-right:480px;}
+.append-13 {padding-right:520px;}
+.append-14 {padding-right:560px;}
+.append-15 {padding-right:600px;}
+.append-16 {padding-right:640px;}
+.append-17 {padding-right:680px;}
+.append-18 {padding-right:720px;}
+.append-19 {padding-right:760px;}
+.append-20 {padding-right:800px;}
+.append-21 {padding-right:840px;}
+.append-22 {padding-right:880px;}
+.append-23 {padding-right:920px;}
+.prepend-1 {padding-left:40px;}
+.prepend-2 {padding-left:80px;}
+.prepend-3 {padding-left:120px;}
+.prepend-4 {padding-left:160px;}
+.prepend-5 {padding-left:200px;}
+.prepend-6 {padding-left:240px;}
+.prepend-7 {padding-left:280px;}
+.prepend-8 {padding-left:320px;}
+.prepend-9 {padding-left:360px;}
+.prepend-10 {padding-left:400px;}
+.prepend-11 {padding-left:440px;}
+.prepend-12 {padding-left:480px;}
+.prepend-13 {padding-left:520px;}
+.prepend-14 {padding-left:560px;}
+.prepend-15 {padding-left:600px;}
+.prepend-16 {padding-left:640px;}
+.prepend-17 {padding-left:680px;}
+.prepend-18 {padding-left:720px;}
+.prepend-19 {padding-left:760px;}
+.prepend-20 {padding-left:800px;}
+.prepend-21 {padding-left:840px;}
+.prepend-22 {padding-left:880px;}
+.prepend-23 {padding-left:920px;}
+.border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;}
+.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;}
+.pull-1 {margin-left:-40px;}
+.pull-2 {margin-left:-80px;}
+.pull-3 {margin-left:-120px;}
+.pull-4 {margin-left:-160px;}
+.pull-5 {margin-left:-200px;}
+.pull-6 {margin-left:-240px;}
+.pull-7 {margin-left:-280px;}
+.pull-8 {margin-left:-320px;}
+.pull-9 {margin-left:-360px;}
+.pull-10 {margin-left:-400px;}
+.pull-11 {margin-left:-440px;}
+.pull-12 {margin-left:-480px;}
+.pull-13 {margin-left:-520px;}
+.pull-14 {margin-left:-560px;}
+.pull-15 {margin-left:-600px;}
+.pull-16 {margin-left:-640px;}
+.pull-17 {margin-left:-680px;}
+.pull-18 {margin-left:-720px;}
+.pull-19 {margin-left:-760px;}
+.pull-20 {margin-left:-800px;}
+.pull-21 {margin-left:-840px;}
+.pull-22 {margin-left:-880px;}
+.pull-23 {margin-left:-920px;}
+.pull-24 {margin-left:-960px;}
+.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
+.push-1 {margin:0 -40px 1.5em 40px;}
+.push-2 {margin:0 -80px 1.5em 80px;}
+.push-3 {margin:0 -120px 1.5em 120px;}
+.push-4 {margin:0 -160px 1.5em 160px;}
+.push-5 {margin:0 -200px 1.5em 200px;}
+.push-6 {margin:0 -240px 1.5em 240px;}
+.push-7 {margin:0 -280px 1.5em 280px;}
+.push-8 {margin:0 -320px 1.5em 320px;}
+.push-9 {margin:0 -360px 1.5em 360px;}
+.push-10 {margin:0 -400px 1.5em 400px;}
+.push-11 {margin:0 -440px 1.5em 440px;}
+.push-12 {margin:0 -480px 1.5em 480px;}
+.push-13 {margin:0 -520px 1.5em 520px;}
+.push-14 {margin:0 -560px 1.5em 560px;}
+.push-15 {margin:0 -600px 1.5em 600px;}
+.push-16 {margin:0 -640px 1.5em 640px;}
+.push-17 {margin:0 -680px 1.5em 680px;}
+.push-18 {margin:0 -720px 1.5em 720px;}
+.push-19 {margin:0 -760px 1.5em 760px;}
+.push-20 {margin:0 -800px 1.5em 800px;}
+.push-21 {margin:0 -840px 1.5em 840px;}
+.push-22 {margin:0 -880px 1.5em 880px;}
+.push-23 {margin:0 -920px 1.5em 920px;}
+.push-24 {margin:0 -960px 1.5em 960px;}
+.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;}
+div.prepend-top, .prepend-top {margin-top:1.5em;}
+div.append-bottom, .append-bottom {margin-bottom:1.5em;}
+.box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;}
+hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 17px;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
+.clearfix, .container {display:block;}
+.clear {clear:both;}
View
267 assets/js/jquery.textarea.js.js
@@ -0,0 +1,267 @@
+/*
+ * Tabby jQuery plugin version 0.12
+ *
+ * Ted Devito - http://teddevito.com/demos/textarea.html
+ *
+ * Copyright (c) 2009 Ted Devito
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are 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 of the author may not be used to endorse or promote products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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.
+ *
+ */
+
+// create closure
+
+(function($) {
+
+ // plugin definition
+
+ $.fn.tabby = function(options) {
+ //debug(this);
+ // build main options before element iteration
+ var opts = $.extend({}, $.fn.tabby.defaults, options);
+ var pressed = $.fn.tabby.pressed;
+
+ // iterate and reformat each matched element
+ return this.each(function() {
+ $this = $(this);
+
+ // build element specific options
+ var options = $.meta ? $.extend({}, opts, $this.data()) : opts;
+
+ $this.bind('keydown',function (e) {
+ var kc = $.fn.tabby.catch_kc(e);
+ if (16 == kc) pressed.shft = true;
+ /*
+ because both CTRL+TAB and ALT+TAB default to an event (changing tab/window) that
+ will prevent js from capturing the keyup event, we'll set a timer on releasing them.
+ */
+ if (17 == kc) {pressed.ctrl = true; setTimeout("$.fn.tabby.pressed.ctrl = false;",1000);}
+ if (18 == kc) {pressed.alt = true; setTimeout("$.fn.tabby.pressed.alt = false;",1000);}
+
+ if (9 == kc && !pressed.ctrl && !pressed.alt) {
+ e.preventDefault; // does not work in O9.63 ??
+ pressed.last = kc; setTimeout("$.fn.tabby.pressed.last = null;",0);
+ process_keypress ($(e.target).get(0), pressed.shft, options);
+ return false;
+ }
+
+ }).bind('keyup',function (e) {
+ if (16 == $.fn.tabby.catch_kc(e)) pressed.shft = false;
+ }).bind('blur',function (e) { // workaround for Opera -- http://www.webdeveloper.com/forum/showthread.php?p=806588
+ if (9 == pressed.last) $(e.target).one('focus',function (e) {pressed.last = null;}).get(0).focus();
+ });
+
+ });
+ };
+
+ // define and expose any extra methods
+ $.fn.tabby.catch_kc = function(e) { return e.keyCode ? e.keyCode : e.charCode ? e.charCode : e.which; };
+ $.fn.tabby.pressed = {shft : false, ctrl : false, alt : false, last: null};
+
+ // private function for debugging
+ function debug($obj) {
+ if (window.console && window.console.log)
+ window.console.log('textarea count: ' + $obj.size());
+ };
+
+ function process_keypress (o,shft,options) {
+ var scrollTo = o.scrollTop;
+ //var tabString = String.fromCharCode(9);
+
+ // gecko; o.setSelectionRange is only available when the text box has focus
+ if (o.setSelectionRange) gecko_tab (o, shft, options);
+
+ // ie; document.selection is always available
+ else if (document.selection) ie_tab (o, shft, options);
+
+ o.scrollTop = scrollTo;
+ }
+
+ // plugin defaults
+ $.fn.tabby.defaults = {tabString : String.fromCharCode(9)};
+
+ function gecko_tab (o, shft, options) {
+ var ss = o.selectionStart;
+ var es = o.selectionEnd;
+
+ // when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control
+ if(ss == es) {
+ // SHIFT+TAB
+ if (shft) {
+ // check to the left of the caret first
+ if ("\t" == o.value.substring(ss-options.tabString.length, ss)) {
+ o.value = o.value.substring(0, ss-options.tabString.length) + o.value.substring(ss); // put it back together omitting one character to the left
+ o.focus();
+ o.setSelectionRange(ss - options.tabString.length, ss - options.tabString.length);
+ }
+ // then check to the right of the caret
+ else if ("\t" == o.value.substring(ss, ss + options.tabString.length)) {
+ o.value = o.value.substring(0, ss) + o.value.substring(ss + options.tabString.length); // put it back together omitting one character to the right
+ o.focus();
+ o.setSelectionRange(ss,ss);
+ }
+ }
+ // TAB
+ else {
+ o.value = o.value.substring(0, ss) + options.tabString + o.value.substring(ss);
+ o.focus();
+ o.setSelectionRange(ss + options.tabString.length, ss + options.tabString.length);
+ }
+ }
+ // selections will always add/remove tabs from the start of the line
+ else {
+ // split the textarea up into lines and figure out which lines are included in the selection
+ var lines = o.value.split("\n");
+ var indices = new Array();
+ var sl = 0; // start of the line
+ var el = 0; // end of the line
+ var sel = false;
+ for (var i in lines) {
+ el = sl + lines[i].length;
+ indices.push({start: sl, end: el, selected: (sl <= ss && el > ss) || (el >= es && sl < es) || (sl > ss && el < es)});
+ sl = el + 1;// for "\n"
+ }
+
+ // walk through the array of lines (indices) and add tabs where appropriate
+ var modifier = 0;
+ for (var i in indices) {
+ if (indices[i].selected) {
+ var pos = indices[i].start + modifier; // adjust for tabs already inserted/removed
+ // SHIFT+TAB
+ if (shft && options.tabString == o.value.substring(pos,pos+options.tabString.length)) { // only SHIFT+TAB if there's a tab at the start of the line
+ o.value = o.value.substring(0,pos) + o.value.substring(pos + options.tabString.length); // omit the tabstring to the right
+ modifier -= options.tabString.length;
+ }
+ // TAB
+ else if (!shft) {
+ o.value = o.value.substring(0,pos) + options.tabString + o.value.substring(pos); // insert the tabstring
+ modifier += options.tabString.length;
+ }
+ }
+ }
+ o.focus();
+ var ns = ss + ((modifier > 0) ? options.tabString.length : (modifier < 0) ? -options.tabString.length : 0);
+ var ne = es + modifier;
+ o.setSelectionRange(ns,ne);
+ }
+ }
+
+ function ie_tab (o, shft, options) {
+ var range = document.selection.createRange();
+
+ if (o == range.parentElement()) {
+ // when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control
+ if ('' == range.text) {
+ // SHIFT+TAB
+ if (shft) {
+ var bookmark = range.getBookmark();
+ //first try to the left by moving opening up our empty range to the left
+ range.moveStart('character', -options.tabString.length);
+ if (options.tabString == range.text) {
+ range.text = '';
+ } else {
+ // if that didn't work then reset the range and try opening it to the right
+ range.moveToBookmark(bookmark);
+ range.moveEnd('character', options.tabString.length);
+ if (options.tabString == range.text)
+ range.text = '';
+ }
+ // move the pointer to the start of them empty range and select it
+ range.collapse(true);
+ range.select();
+ }
+
+ else {
+ // very simple here. just insert the tab into the range and put the pointer at the end
+ range.text = options.tabString;
+ range.collapse(false);
+ range.select();
+ }
+ }
+ // selections will always add/remove tabs from the start of the line
+ else {
+
+ var selection_text = range.text;
+ var selection_len = selection_text.length;
+ var selection_arr = selection_text.split("\r\n");
+
+ var before_range = document.body.createTextRange();
+ before_range.moveToElementText(o);
+ before_range.setEndPoint("EndToStart", range);
+ var before_text = before_range.text;
+ var before_arr = before_text.split("\r\n");
+ var before_len = before_text.length; // - before_arr.length + 1;
+
+ var after_range = document.body.createTextRange();
+ after_range.moveToElementText(o);
+ after_range.setEndPoint("StartToEnd", range);
+ var after_text = after_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n
+
+ var end_range = document.body.createTextRange();
+ end_range.moveToElementText(o);
+ end_range.setEndPoint("StartToEnd", before_range);
+ var end_text = end_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n
+
+ var check_html = $(o).html();
+ $("#r3").text(before_len + " + " + selection_len + " + " + after_text.length + " = " + check_html.length);
+ if((before_len + end_text.length) < check_html.length) {
+ before_arr.push("");
+ before_len += 2; // for the \r\n that was trimmed
+ if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length))
+ selection_arr[0] = selection_arr[0].substring(options.tabString.length);
+ else if (!shft) selection_arr[0] = options.tabString + selection_arr[0];
+ } else {
+ if (shft && options.tabString == before_arr[before_arr.length-1].substring(0,options.tabString.length))
+ before_arr[before_arr.length-1] = before_arr[before_arr.length-1].substring(options.tabString.length);
+ else if (!shft) before_arr[before_arr.length-1] = options.tabString + before_arr[before_arr.length-1];
+ }
+
+ for (var i = 1; i < selection_arr.length; i++) {
+ if (shft && options.tabString == selection_arr[i].substring(0,options.tabString.length))
+ selection_arr[i] = selection_arr[i].substring(options.tabString.length);
+ else if (!shft) selection_arr[i] = options.tabString + selection_arr[i];
+ }
+
+ if (1 == before_arr.length && 0 == before_len) {
+ if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length))
+ selection_arr[0] = selection_arr[0].substring(options.tabString.length);
+ else if (!shft) selection_arr[0] = options.tabString + selection_arr[0];
+ }
+
+ if ((before_len + selection_len + after_text.length) < check_html.length) {
+ selection_arr.push("");
+ selection_len += 2; // for the \r\n that was trimmed
+ }
+
+ before_range.text = before_arr.join("\r\n");
+ range.text = selection_arr.join("\r\n");
+
+ var new_range = document.body.createTextRange();
+ new_range.moveToElementText(o);
+
+ if (0 < before_len) new_range.setEndPoint("StartToEnd", before_range);
+ else new_range.setEndPoint("StartToStart", before_range);
+ new_range.setEndPoint("EndToEnd", range);
+
+ new_range.select();
+
+ }
+ }
+ }
+
+// end of closure
+})(jQuery);
View
65 components/AppManagerConfig.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Works with the Yii config.
+ */
+class AppManagerConfig extends AppManagerNode
+{
+ /**
+ * @var string full path
+ * @see CConfig::loadFromFile()
+ */
+ protected $location;
+
+ /**
+ * @param string $location
+ */
+ public function __construct($location=null)
+ {
+ $this->loadFromFile($location);
+ }
+
+ /**
+ * @param string $configFile
+ */
+ public function loadFromFile($configFile)
+ {
+ $this->setLocation($configFile);
+ parent::loadFromFile($configFile);
+ }
+
+ /**
+ * Updates config file.
+ * @return bool
+ */
+ public function save()
+ {
+ return (bool)@file_put_contents($this->getLocation(),
+ (string)$this->getWriter());
+ }
+
+ /**
+ * @param string $location
+ */
+ public function setLocation($location)
+ {
+ $this->location = $location;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLocation()
+ {
+ return $this->location;
+ }
+
+ /**
+ * Gets helper that is able to generate a config file.
+ * @return AppManagerWriter
+ */
+ protected function getWriter()
+ {
+ Yii::import('appManager.components.writer.*');
+ return new AppManagerWriter($this);
+ }
+}
View
277 components/AppManagerController.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * Common controller.
+ */
+class AppManagerController extends CController
+{
+ /**
+ * @var string
+ */
+ public $layout = null;
+ /**
+ * @var array context menu items. This property will be assigned to {@link CMenu::items}.
+ */
+ public $menu=array();
+ /**
+ * @var array the breadcrumbs of the current page. The value of this property will
+ * be assigned to {@link CBreadcrumbs::links}. Please refer to {@link CBreadcrumbs::links}
+ * for more details on how to specify this property.
+ */
+ public $breadcrumbs=array();
+
+ /**
+ * @var string section title.
+ */
+ protected $title;
+ /**
+ * @var string
+ */
+ protected $entityClass;
+ /**
+ * @var AppManagerEntity is used for all pages except the listing.
+ */
+ protected $entity;
+
+ /**
+ * Shows list of entities.
+ */
+ public function actionIndex()
+ {
+ $this->render($this->getBaseViewPath() . 'index',
+ array('entities' => $this->createEntity()->search()));
+ }
+
+ /**
+ * Shows a specific entity.
+ * @param string $id
+ */
+ public function actionView($id)
+ {
+ $this->render($this->getBaseViewPath() . 'view',
+ array('entity' => $this->getEntity()));
+ }
+
+ /**
+ * Shows an edit page.
+ * Performs saving and restoring.
+ * @param string $id
+ */
+ public function actionUpdate($id)
+ {
+ $entity = $this->getEntity();
+ if (!$entity->canUpdate()) {
+ $this->setEntityFlash('error', 'Unable to update {name}.');
+ $this->redirect(array('index'));
+ }
+
+ if ($this->needRestore()) {
+ if ($entity->restore()) {
+ $this->setEntityFlash('success', '{name} has been restored.');
+ } else {
+ $this->setEntityFlash('error', 'Unable to restore {name}.');
+ }
+ } elseif ($data = $this->getPost($this->getEntityClass())) {
+ $entity->attributes = $data;
+ $entity->options = $this->getPost($this->getOptionClass());
+
+ if ($entity->save()) {
+ $this->setEntityFlash('success', '{name} has been updated.');
+ $this->redirect(array('index'));
+ } else {
+ $this->setEntityFlash('error', 'Unable to update {name}.');
+ }
+ }
+
+ $this->render($this->getBaseViewPath() . 'update',
+ array('entity' => $entity));
+ }
+
+ /**
+ * Determines if the current update request should be used for restoring.
+ * @return bool
+ */
+ protected function needRestore()
+ {
+ return (bool)$this->getPost('restore');
+ }
+
+ /**
+ * @param string $id
+ */
+ public function actionActivate($id)
+ {
+ if ($this->getEntity()->activate()) {
+ $this->setEntityFlash('success', '{name} has been activated.');
+ $this->redirect(array('update', 'id' => $id));
+ } else {
+ $this->setEntityFlash('error', 'Unable to activate {name}.');
+ $this->redirect(array('index'));
+ }
+ }
+
+ /**
+ * @param string $id
+ */
+ public function actionDeactivate($id)
+ {
+ if ($this->getEntity()->deactivate()) {
+ $this->setEntityFlash('success', '{name} has been deactivated.');
+ } else {
+ $this->setEntityFlash('error', 'Unable to deactivate {name}.');
+ }
+ $this->redirect(array('index'));
+ }
+
+ /**
+ * @param string $id
+ */
+ public function actionDelete($id)
+ {
+ if ($this->getEntity()->delete()) {
+ $this->setEntityFlash('success', '{name} has been deleted.');
+ } else {
+ $this->setEntityFlash('error', 'Unable to delete {name}.');
+ }
+ $this->redirect(array('index'));
+ }
+
+ /**
+ * Forms page title.
+ * @return string
+ */
+ public function getPageTitle()
+ {
+ return $this->module->name . ' - ' . $this->getTitle();
+ }
+
+ /**
+ * Gets localized section title.
+ * @return string
+ */
+ public function getTitle()
+ {
+ return AppManagerModule::t($this->title);
+ }
+
+ /**
+ * Gets an entity instance using request params.
+ * @return AppManagerEntity
+ * @throws CHttpException if entity can't be created.
+ */
+ public function getEntity()
+ {
+ if (null === $this->entity) {
+ if (!$id = $this->getParam('id')) {
+ throw new CHttpException(404,
+ AppManagerModule::t('Can\'t resolve an empty id.'));
+ }
+ $this->entity = $this->createEntity($this->getParam('id'));
+ }
+ return $this->entity;
+ }
+
+ /**
+ * Checks if controller id equals to needed.
+ * Mostly used for view.
+ * @param string $id controller id.
+ * @return bool
+ */
+ public function isId($id)
+ {
+ return ($id === $this->getId());
+ }
+
+ /**
+ * Creates an entity instance.
+ * @param string $id
+ * @return AppManagerEntity
+ * @throws CHttpException in case when $id was set,
+ * but the entity can't be created.
+ */
+ protected function createEntity($id = null)
+ {
+ $class = $this->getEntityClass();
+ $entity = new $class;
+ try {
+ $entity = $entity->findById($id);
+ } catch (CException $e) {
+ throw new CHttpException(404, $e->getMessage());
+ }
+ return $entity;
+ }
+
+ /**
+ * Gets path to common view directory.
+ * Should be empty if a subclass wants to use default by Yii templates directory.
+ * @return string
+ */
+ protected function getBaseViewPath()
+ {
+ return '../default/';
+ }
+
+ /**
+ * Gets data from POST request.
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
+ protected function getPost($name, $default = null)
+ {
+ return Yii::app()->getRequest()->getPost($name, $default);
+ }
+
+ /**
+ * Gets data from GET request.
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
+ protected function getParam($name, $default = null)
+ {
+ return Yii::app()->getRequest()->getParam($name, $default);
+ }
+
+ /**
+ * Determines class name for entity options.
+ * Used for dealing with CActiveForm form elements naming convention.
+ * @return string
+ */
+ protected function getOptionClass()
+ {
+ return get_class($this->getEntity()->getOptions());
+ }
+
+ /**
+ * @return string
+ */
+ protected function getEntityClass()
+ {
+ return $this->entityClass;
+ }
+
+ /**
+ * Sets a flash message related to the entity.
+ * @param string $flashType
+ * @param string $message
+ * @see AppManagerController::setFlash() for flash types.
+ */
+ protected function setEntityFlash($flashType, $message)
+ {
+ $this->setFlash($flashType, $message,
+ array('{name}' => $this->getEntity()->name));
+ }
+
+ /**
+ * Wrapper to Yii user->setFlash() functionality.
+ * All messages will be localized for AppManager module.
+ * @param string $type could be: 'success', 'notice', 'error'.
+ * @param string $message with placeholders.
+ * @param array $params placeholders values.
+ */
+ protected function setFlash($type, $message, $params = null)
+ {
+ Yii::app()->user->setFlash('AppManager.' . $type,
+ AppManagerModule::t($message, $params));
+ }
+}
View
756 components/AppManagerEntity.php
@@ -0,0 +1,756 @@
+<?php
+/**
+ * Base class for all entities.
+ */
+abstract class AppManagerEntity extends AppManagerModel
+{
+ /**
+ * @var string unique identifier.
+ */
+ protected $id;
+ /**
+ * @var string human-readable name.
+ */
+ protected $name;
+ /**
+ * @var string full description.
+ */
+ protected $desc;
+ /**
+ * @var string short description.
+ */
+ protected $shortDesc;
+ /**
+ * @var string author's details.
+ */
+ protected $author;
+ /**
+ * @var string entity web reference.
+ */
+ protected $link;
+ /**
+ * @var string the class name.
+ */
+ protected $className;
+ /**
+ * @var string the class name with a full alias path.
+ */
+ protected $fullClassName;
+ /**
+ * @var bool whether entity is activated.
+ */
+ protected $isActive;
+ /**
+ * @var AppManagerOptions list of options.
+ */
+ protected $options;
+
+ /**
+ * @var string full path to the entity file or directory.
+ */
+ private $_path;
+ /**
+ * @var string full path to the entity file.
+ */
+ private $_fileName;
+ /**
+ * @var AppManagerParser reflection handler.
+ */
+ private $_parser;
+ /**
+ * @var AppManagerConfig main settings.
+ */
+ private $_config;
+
+ /**
+ * Initialization.
+ * @param string $path full path or with aliases.
+ */
+ public function __construct($path = null)
+ {
+ $this->setPath($path);
+ }
+
+ /**
+ * Searches available entities.
+ * @return CArrayDataProvider
+ */
+ public function search()
+ {
+ $entities = $this->scanDirs();
+ foreach ($entities as &$entity) {
+ $entity = $this->createEntity($entity);
+ }
+ $entities = array_merge($entities, $this->getDefaultEntities());
+ return $this->createDataProvider($entities);
+ }
+
+ /**
+ * @return bool
+ */
+ public function activate()
+ {
+ if (!$this->canActivate()) {
+ return false;
+ }
+ $this->loadConfigSection()->add($this->getId(), array(
+ 'class' => $this->getFullClassName(),
+ ));
+ return $this->saveConfig();
+ }
+
+ /**
+ * @return bool
+ */
+ public function deactivate()
+ {
+ if (!$this->canDeactivate()) {
+ return false;
+ }
+ $this->loadConfigSection()->remove($this->getId());
+ return $this->saveConfig();
+ }
+
+ /**
+ * Completely deletes the entity.
+ * @return bool
+ */
+ public function delete()
+ {
+ if (!$this->canDelete()) {
+ return false;
+ }
+ }
+
+ /**
+ * Saves entity and all options.
+ * @return bool
+ */
+ public function save()
+ {
+ if (!$this->canUpdate() || !$this->validate()) {
+ return false;
+ }
+ $config = $this->getConfig();
+ $config->add('class', $this->getFullClassName());
+ if (!$this->getOptions()->updateConfig()) {
+ return false;
+ }
+ return $this->saveConfig();
+ }
+
+ /**
+ * Validation rules.
+ * @return array
+ */
+ public function rules()
+ {
+ return array(
+ array('fullClassName', 'required'),
+ array('fullClassName', 'validClass'),
+ );
+ }
+
+ /**
+ * Checks if attribute is an existed class
+ * @param string $attribute
+ */
+ public function validClass($attribute)
+ {
+ $path = Yii::getPathOfAlias($this->$attribute);
+ if (!file_exists($path . '.php')) {
+ $this->addError($attribute,
+ AppManagerModule::t('Class does not exist.'));
+ }
+ }
+
+ /**
+ * Restores options and the class name to defaults.
+ * @return bool
+ */
+ public function restore()
+ {
+ if (!$this->canRestore()) {
+ return false;
+ }
+ $config = $this->getConfig();
+ $config->clear();
+ $config->add('class', $this->getFullClassName());
+ return $this->saveConfig();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId()
+ {
+ if (null === $this->id) {
+ $this->id = $this->createId();
+ }
+ return $this->id;
+ }
+
+ /**
+ * Forms unique identifier.
+ * @return string
+ */
+ protected function createId()
+ {
+ $name = $this->getName();
+ $name[0] = strtolower($name[0]);
+ return (string)$name;
+ }
+
+ /**
+ * Forms entity for id.
+ * @param string $id
+ * @return AppManagerEntity
+ */
+ public function findById($id)
+ {
+ if ($path = $this->resolveId($id)) {
+ $this->setPath($path);
+ }
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @see CButtonColumn
+ */
+ public function getPrimaryKey()
+ {
+ return $this->getId();
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = ucfirst($name);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ if (null === $this->name) {
+ $this->setName($this->resolveName());
+ }
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setClassName($name)
+ {
+ $this->className = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClassName()
+ {
+ if (null === $this->className) {
+ $this->className = $this->resolveClassName();
+ }
+ return $this->className;
+ }
+
+ /**
+ * Gets class name for the original entity.
+ * @return string
+ */
+ public function getDefaultClassName()
+ {
+ return $this->getParser()->getClassName();
+ }
+
+ /**
+ * @return string
+ */
+ public function getFullClassName()
+ {
+ if (null === $this->fullClassName) {
+ $this->fullClassName = $this->normalizeClassName();
+ }
+ return $this->fullClassName;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setFullClassName($name)
+ {
+ $this->fullClassName = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDesc()
+ {
+ if (null === $this->desc) {
+ $this->desc = $this->getParser()->getDesc();
+ }
+ return $this->desc;
+ }
+
+ /**
+ * @return string
+ */
+ public function getShortDesc()
+ {
+ if (null === $this->shortDesc) {
+ $this->shortDesc = $this->getParser()->getShortDesc();
+ }
+ return $this->shortDesc;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuthor()
+ {
+ if (null === $this->author) {
+ $this->author = $this->getParser()->getAuthor();
+ }
+ return $this->author;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLink()
+ {
+ if (null === $this->link) {
+ $this->link = $this->getParser()->getLink();
+ }
+ return $this->link;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getIsActive()
+ {
+ if (null === $this->isActive) {
+ $this->isActive = (bool)$this->getConfig()->count();
+ }
+ return $this->isActive;
+ }
+
+ /**
+ * Checks if the entity belongs to Yii core.
+ * @return bool
+ */
+ public function getIsCore()
+ {
+ return (bool)$this->getCorePath($this->getId());
+ }
+
+ /**
+ * Determines if the entity can BE activated.
+ * @return bool
+ */
+ public function canActivate()
+ {
+ return !$this->getIsActive();
+ }
+
+ /**
+ * @return bool
+ */
+ public function canDeactivate()
+ {
+ return $this->getIsActive();
+ }
+
+ /**
+ * @return bool
+ */
+ public function canUpdate()
+ {
+ return $this->getIsActive();
+ }
+
+ /**
+ * @return bool
+ */
+ public function canDelete()
+ {
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function canRestore()
+ {
+ return ($this->canUpdate() && ($this->getConfig()->count() > 1));
+ }
+
+ /**
+ * Sets options from input data.
+ * @param array $options
+ */
+ public function setOptions($options)
+ {
+ $this->getOptions()->attributes = $options;
+ }
+
+ /**
+ * @return AppManagerOptions
+ */
+ public function getOptions()
+ {
+ if (null === $this->options) {
+ $options = new AppManagerOptions;
+ $options->setParser($this->getParser());
+ $options->setConfig($this->getConfig());
+ $this->options = $options;
+ }
+ return $this->options;
+ }
+
+ /**
+ * @return AppManagerOptions
+ */
+ public function getOptionsProvider()
+ {
+ return $this->getOptions()->getProvider();
+ }
+
+ /**
+ * @param string $path full path with directory separators or with aliases.
+ */
+ public function setPath($path)
+ {
+ if (!empty($path)) {
+ $this->_path = $this->resolveFullPath($path);
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->_path;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFileName()
+ {
+ if (null === $this->_fileName) {
+ $this->_fileName = $this->resolveFileName($this->getPath());
+ }
+ return $this->_fileName;
+ }
+
+ /**
+ * @return AppManagerParser
+ */
+ protected function getParser()
+ {
+ if (null === $this->_parser) {
+ $this->_parser = new AppManagerParser($this->getFileName());
+ }
+ return $this->_parser;
+ }
+
+ /**
+ * @return AppManagerConfig
+ */
+ protected function getConfig()
+ {
+ if (null === $this->_config) {
+ $this->_config = $this->loadConfig();
+ }
+ return $this->_config;
+ }
+
+ /**
+ * Scans all set directories.
+ * @return array list of AppManagerEntity
+ */
+ protected function scanDirs()
+ {
+ $result = array();
+ foreach ($this->getScanDirs() as $dir) {
+ $result = array_merge($result, $this->scanDir($dir));
+ }
+ return $result;
+ }
+
+ /**
+ * Finds entities only in one directory.
+ * @param string $dir
+ * @return array list of AppManagerEntity
+ */
+ protected function scanDir($dir)
+ {
+ $entities = scandir($dir);
+ unset($entities[0], $entities[1]);
+ foreach ($entities as &$entity) {
+ $entity = $dir . DIRECTORY_SEPARATOR . $entity;
+ }
+ return (array)$entities;
+ }
+
+ /**
+ * Gets list of directories that need to find in.
+ * @return array empty for abstraction
+ */
+ protected function getScanDirs()
+ {
+ return array();
+ }
+
+ /**
+ * Gets list of entities that need to be added without scan.
+ * @return array list of AppManagerEntity
+ */
+ protected function getDefaultEntities()
+ {
+ $entities = array();
+ foreach ($this->getCoreList() as $alias => $path) {
+ $entities[] = $this->createEntity()->findById($alias);
+ }
+ return $entities;
+ }
+
+ /**
+ * Gets full path to a core entity.
+ * @param string $id
+ * @return string
+ */
+ protected function getCorePath($id)
+ {
+ $core = $this->getCoreList();
+ return isset($core[$id]) ? $this->formCorePath($core[$id]) : null;
+ }
+
+ /**
+ * Gets list of core entities.
+ * @return array
+ */
+ protected function getCoreList()
+ {
+ return array();
+ }
+
+ /**
+ * Creates path to core entity.
+ * @param string $path
+ * @return string
+ */
+ protected function formCorePath($path)
+ {
+ return 'system.' . $path;
+ }
+
+ /**
+ * Finds full path using supplied.
+ * @param string $path could be with Yii aliases
+ * @return string
+ */
+ protected function resolveFullPath($path)
+ {
+ if ($fullPath = Yii::getPathOfAlias($path)) {
+ if (is_file($fullPath . '.php')) {
+ $fullPath.= '.php';
+ }
+ } else {
+ $fullPath = $path;
+ }
+ return $fullPath;
+ }
+
+ /**
+ * Finds full path to entity file.
+ * @param string $path
+ * @return string
+ * @throws CException in case if file does not exist
+ */
+ protected function resolveFileName($path)
+ {
+ if (false === strpos($path, '.')) {
+ $path .= DIRECTORY_SEPARATOR . $this->formFileName(basename($path));
+ }
+ if (!file_exists($path)) {
+ throw new CException(AppManagerModule::t('{path} does not exist.',
+ array('{path}' => $path)));
+ }
+ return $path;
+ }
+
+ /**
+ * @param string $id
+ * @return AppManagerEntity
+ * @throws CException in case if an entity does not exist
+ */
+ protected function resolveId($id)
+ {
+ if (empty($id)) {
+ return false;
+ }
+ if ($path = $this->getCorePath($id)) {
+ $this->setName($id);
+ return $path;
+ }
+
+ foreach ($this->scanDirs() as $entity) {
+ if (strtolower(basename($entity, '.php')) === strtolower($id)) {
+ return $entity;
+ }
+ }
+ throw new CException(AppManagerModule::t('{id} does not exist.',
+ array('{id}' => $id)));
+ }
+
+ /**
+ * Forms name.
+ * @return string
+ */
+ protected function resolveName()
+ {
+ return basename($this->getFileName(), '.php');
+ }
+
+ /**
+ * Forms class name.
+ * @return string
+ */
+ protected function resolveClassName()
+ {
+ $name = $this->resolveName();
+ $tmp = explode('.', $name);
+ return array_pop($tmp);
+ }
+
+ /**
+ * Forms file name.
+ * @param string $name
+ * @return string
+ */
+ protected function formFileName($name)
+ {
+ return $name . '.php';
+ }
+
+ /**
+ * Creates full class name.
+ * @return string
+ */
+ protected function normalizeClassName()
+ {
+ if ($this->getIsCore()) {
+ return $this->getCorePath($this->getId());
+ } elseif (false === strpos($this->getClassName(), '.')) {
+ return $this->formFullClassName();
+ }
+ return $this->getClassName();
+ }
+
+ /**
+ * Forms full class name
+ * @return string
+ */
+ protected function formFullClassName()
+ {
+ return $this->getClassName();
+ }
+
+ /**
+ * Creates entity instance.
+ * @param string $path
+ * @return AppManagerEntity
+ */
+ protected function createEntity($path = null)
+ {
+ $class = get_class($this);
+ return new $class($path);
+ }
+
+ /**
+ * Loads main config.
+ * Creates an empty if does not exist.
+ * @return AppManagerConfig
+ */
+ protected function loadConfig()
+ {
+ $config = $this->loadConfigSection();
+ $name = $this->getId();
+ $current = $config->itemAt($name);
+ if (null === $current) {
+ $key = $config->search($name);
+ if (false !== $key) { //normalize config
+ $config->remove($key);
+ $config->add($name, array(
+ 'class' => $this->getFullClassName(),
+ ));
+ } else {
+ $config->add($name, array());
+ }
+ $current = $config->itemAt($name);
+ }
+ return $current;