Overview

Packages

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

Classes

  • ThemeBuildCommand
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: 
  3: /*****************************************************************************************
  4:  * X2Engine Open Source Edition is a customer relationship management program developed by
  5:  * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
  6:  * 
  7:  * This program is free software; you can redistribute it and/or modify it under
  8:  * the terms of the GNU Affero General Public License version 3 as published by the
  9:  * Free Software Foundation with the addition of the following permission added
 10:  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 11:  * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
 12:  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 13:  * 
 14:  * This program is distributed in the hope that it will be useful, but WITHOUT
 15:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 16:  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 17:  * details.
 18:  * 
 19:  * You should have received a copy of the GNU Affero General Public License along with
 20:  * this program; if not, see http://www.gnu.org/licenses or write to the Free
 21:  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 22:  * 02110-1301 USA.
 23:  * 
 24:  * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
 25:  * California 95067, USA. or at email address contact@x2engine.com.
 26:  * 
 27:  * The interactive user interfaces in modified source and object code versions
 28:  * of this program must display Appropriate Legal Notices, as required under
 29:  * Section 5 of the GNU Affero General Public License version 3.
 30:  * 
 31:  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 32:  * these Appropriate Legal Notices must retain the display of the "Powered by
 33:  * X2Engine" logo. If the display of the logo is not reasonably feasible for
 34:  * technical reasons, the Appropriate Legal Notices must display the words
 35:  * "Powered by X2Engine".
 36:  *****************************************************************************************/
 37: 
 38: Yii::import('application.components.util.*');
 39: 
 40: /**
 41:  * Builds a php theme file from various @theme tags in all CSS files. 
 42:  * The syntax for a theme tage is 
 43:  *   /* @theme <rule>: <key> */ 
 44:  /* Where <rule> is a css rule that takes a color argument, 
 45:  * and <key> is a themeGenerator key found at the bottom of {@link ThemeGenerator}
 46:  * This script will find all the tags and accumulate the rules in a php file
 47:  *
 48:  * @package application.commands
 49:  * @author Alex Rowe <alex@x2engine.com>
 50:  */
 51: class ThemeBuildCommand extends CConsoleCommand {
 52: 
 53:     /**
 54:      * @var string Input directory of the css Root
 55:      */
 56:     public $inputDir = '../';
 57: 
 58:     /**
 59:      * @var string Output file
 60:      */
 61:     public $outputFile = 'components/ThemeGenerator/templates/generatedRules.php';
 62:     public $moduleOverridesFile = 
 63:         'components/ThemeGenerator/templates/generatedModuleOverrides.php';
 64: 
 65:     /**
 66:      * Entry point
 67:      */
 68:     public function run($args) {
 69:         if (isset($args[0])) {
 70:             $this->inputDir = $args[0];
 71:         }
 72: 
 73:         if (isset($args[1])) {
 74:             $this->outputFile = $args[1];
 75:         }
 76: 
 77:         if (isset($args[0]) && $args[0] == '--keys') {
 78:             echo "These are the avaliable theming keys\n";
 79: 
 80:             foreach(ThemeGenerator::getProfileKeys() as $key) {
 81:                 echo "$key\n";
 82:             }
 83: 
 84:             return;
 85:         }
 86: 
 87:         echo "Building theme...\n";
 88:         // First, we recieve a list of all CSS files
 89:         $paths = $this->getCssFiles ($this->inputDir);
 90: 
 91:         $length = count($paths);
 92: 
 93:         if ($length < 1) {
 94:             echo "Error: no Css files found in directory: $this->inputDir";
 95:             return;
 96:         }
 97: 
 98:         echo "$length css files found\n";
 99:         echo "Scanning for theme tags\n";
100:         $counter = 0.0;
101: 
102:         // Now, we collect the rules from each file, merging duplicate entries
103:         $matches = array();
104:         foreach ($paths as $i => $path) {
105:             $matches = array_merge($matches, $this->scanCssFile($path));
106:             
107:             // print loading status....
108:             while ($counter < $i/$length) {
109:                 $counter+= 0.1;
110:                 $this->progressBar($counter);
111:             }
112: 
113:         }
114: 
115:         $matchesLength = count($matches);
116:         echo "\r$matchesLength rules found     \n";
117: 
118:         if ($matchesLength < 1) {
119:             echo "No rules found, aborting\n";
120:             return;
121:         }
122: 
123:         echo "Formatting rules...\n";
124: 
125:         // Finally, construct a string from all of the rules
126:         $output = "<?php ".
127:         "/* This file is generated by ThemeBuildCommand.php. Do not edit manually */\n".
128:         "return \"\n"; // php header
129:         foreach ($matches as $selector => $rule) {
130:             $output .= $this->formatRule($selector, $rule);
131:         }
132:         $output .= "\n \"; ?>"; // Footer
133: 
134:         $this->writeFile ($this->outputFile, $output);
135: 
136:         // generate template for module-specific theming
137:         $output = "<?php ".
138:         "/* This file is generated by ThemeBuildCommand.php. Do not edit manually */\n".
139:         "return \"\n"; // php header
140:         foreach ($matches as $selector => $rule) {
141:             $output .= $this->formatModuleOverridesRule($selector, $rule);
142:         }
143:         $output .= "\n \"; ?>"; // Footer
144: 
145:         $this->writeFile ($this->moduleOverridesFile, $output);
146:     }
147: 
148:     public function writeFile ($outputFile, $output) {
149:         // Check for changes
150:         if (file_exists($outputFile) && sha1_file($outputFile) == sha1($output)) {
151:             echo "No changes detected in $outputFile, Aborting\n";
152:             return;
153:         }
154: 
155:         echo "Saving to $outputFile\n";
156:         file_put_contents ($outputFile, $output);
157:     }
158: 
159:     /**
160:      * Gets a list of all css files in the directory, recusrively
161:      * @param $root string Path of the root directoy
162:      * @return array list of full paths
163:      */
164:     public function getCssFiles($root) {
165:         $iter = new RecursiveIteratorIterator(
166:             new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS),
167:             RecursiveIteratorIterator::SELF_FIRST,
168:             RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied"
169:         );
170: 
171:         $paths = array($root);
172:         foreach ($iter as $path => $dir) {
173:             if (preg_match('/\.css$/', $path)) {
174:                 $paths[] = $path;
175:             }
176:         }
177: 
178:         return $paths;
179:     }
180: 
181:     /**
182:      * Scans a css file and for theme tags and formats an array of rules
183:      * @param $path string pathname of a file to scan
184:      * @return Array of rules in the following format: 
185:      *       '<Selector>' =>                    // ex. div.icon
186:      *               'comments' => 
187:      *                     '<comment1>',        // ex. line 223 of css
188:      *                     ...
189:      *               0 =>   
190:      *                     'rule' => <rule>     // ex. background
191:      *                     'value' => <value>   // ex. darker_link
192:      *               ...
193:      */
194:     public function scanCssFile($path) {
195:         $handle = fopen($path, "r");
196:         $rules = array();
197: 
198:         $lineNumber = -1;
199:         while (($line = fgets($handle)) !== false) {
200:             $lineNumber++;
201:             if (!preg_match('/@theme/', $line)) continue;
202: 
203:             list($selector, $comment, $rule) = $this->makeRule($path, $lineNumber);
204: 
205:             // Create a new rule if its not already in the list
206:             if (!isset($rules[$selector])) {
207:                 $rules[$selector] = array($rule);
208:                 $rules[$selector]['comments'] = array($comment);
209:                 continue;
210:             } 
211: 
212:             // checks for duplicate rules
213:             if (in_array($rule, $rules[$selector])) continue;
214:             $rules[$selector][] = $rule;
215: 
216:             // checks for duplicate comments
217:             if (in_array($comment, $rules[$selector]['comments'])) continue;
218:             $rules[$selector]['comments'][] = $comment;
219:         }       
220: 
221:         fclose($handle);
222: 
223:         return $rules;
224:     }
225: 
226:     /**
227:      * @param $file string pathname of a css file
228:      * @param $lineNumber int lineNumber of the theme tag
229:      * @return array of needed items to construct the array seen in {@link scanCssFile}.
230:      */
231:     public function makeRule($file, $lineNumber) {
232:         $lines = file($file);
233:         $themeLine = $lines[$lineNumber];
234:         // print_r($themeLine);/
235:         $stripped = preg_replace('/.*@theme\ *(.*)\*\//', '\1', $themeLine);
236: 
237:         // Remove extra spaces in between
238:         $stripped = preg_replace('/\ \ */', ' ', $stripped);
239:         $stripped = preg_replace('/:/', '', $stripped);
240:         $params = explode(' ', $stripped);
241: 
242:         // Backtrack the last comment
243:         while(!preg_match('/line\ [0-9]+/', $lines[$lineNumber])) {
244:             $lineNumber--;
245: 
246:             if ($lineNumber < 0) {
247:                 throw new Exception("Backtracked and found no comment in $file", 1);
248:             }
249:         }
250: 
251:         $comment = $lines[$lineNumber];
252:         $selector = '';
253: 
254:         // Move forward lines and append the selector on each line
255:         while(!preg_match('/{/', $lines[$lineNumber])) {
256:             $lineNumber++;
257:             $selector .= $lines[$lineNumber];
258:         }
259: 
260:         // tab indent to look nice
261:         $selector = preg_replace('/\n[^$]/', "\n    ", $selector);
262: 
263:         $rule = $params[0];
264:         $value = $params[1];
265: 
266:         // Throw exception if it is not a valid key
267:         if (!in_array($value, ThemeGenerator::getProfileKeys())) {
268:             $comment = preg_replace('/\/\*(.*)\*\//', '\1', $comment);
269:             throw new Exception("\nTheme Key '$value' is not a valid key.\nFound at$comment");
270:         }
271: 
272:         return array ( 
273:                 $selector, 
274:                 $comment, 
275:                 array (
276:                     'rule' => $rule, 
277:                     'value' => $value
278:                 )
279:             );
280: 
281:     }
282: 
283:     /**
284:      * Formats a rule array into CSS
285:      * @param string $selector CSS selector to put the rules under
286:      * @param string $rule array of comments and items to put into the CSS
287:      */
288:     public function formatRule($selector, $rule, $addNoThemeRule=false) {
289:         // Comments is a 'special' entry in the array, so we take it out before iterating
290:         $comments = $rule['comments'];
291:         unset($rule['comments']);
292:         $string = "\n";
293: 
294:         foreach($comments as $index => $comment) {
295:             $string .= "    $comment";
296:         }
297: 
298:         $string .= "    $selector";
299: 
300:         // Insert a rule so a no-theme class overrides theme
301:         // don't enable this unless all supported browsers support the ":not(X)" css rule
302:         if ($addNoThemeRule)
303:             $string = preg_replace('/\ *{/', ':not(.no-theme) {', $string);
304: 
305:         foreach($rule as $value) {
306:             if (preg_match ('/_override$/', $value['value'])) {
307:                 $string .= "        ".
308:                     '".((isset ($colors[\''.$value['value'].'\']) && '.
309:                         '$colors[\''.$value['value'].'\']) ? '.
310:                             '"'. $value['rule'].': $colors['.$value['value'].']" : "")."'."\n";
311:             } else {
312:                 $string .= "        ".$value['rule'].': $colors['.$value['value']."]\n";
313:             }
314:         }
315:         $string .= "    }\n";
316: 
317:         return $string;
318:     }
319: 
320:     /**
321:      * Formats rules for module-specific theming. Detects module theme override rules and converts
322:      * them into templated rules. Uses caching to prevent insertion of duplicate rules.
323:      */
324:     public function formatModuleOverridesRule($selector, $rule) {
325:         static $rulesCache = array ();
326: 
327:         // This is brittle, but trying to detect module names in selectors would be much 
328:         // trickier
329:         $selector = preg_replace ('/(page-title\.)[a-zA-Z0-9]+/', '$1{\$module}', $selector);
330:         $selector = preg_replace ('/(widget-title-bar\.)[a-zA-Z0-9]+/', '$1{\$module}', $selector);
331: 
332:         if (!isset ($rulesCache[$selector])) $rulesCache[$selector] = array ();
333: 
334:         // Comments is a 'special' entry in the array, so we take it out before iterating
335:         $comments = $rule['comments'];
336:         unset($rule['comments']);
337:         $string = "\n";
338: 
339:         foreach($comments as $index => $comment) {
340:             $string .= "    $comment";
341:         }
342: 
343:         $string .= "    $selector";
344: 
345:         $foundRule = false;
346:         foreach($rule as $value) {
347:             if (preg_match ('/_override$/', $value['value'])) {
348:                 $templated = preg_replace (
349:                     '/^([a-zA-Z0-9]+_)+[^_]+(_override)$/', '$1{\$module}$2', $value['value']);
350:                 if (!isset ($rulesCache[$selector][$templated])) {
351:                     $foundRule = true;
352:                     //$string .= "        ".$value['rule'].': {$colors["'.$templated."\"]}\n";
353:                     $string .= "        ".
354:                         '".((isset ($colors["'.$templated.'"]) && '.
355:                             '$colors["'.$templated.'"]) ? '.
356:                                 '"'. $value['rule'].': {$colors["'.$templated.'"]}" : "")."'."\n";
357:                     $rulesCache[$selector][$templated] = true;
358:                 }
359:             } 
360:         }
361:         $string .= "    }\n";
362: 
363:         if (!$foundRule) $string = '';
364:         return $string;
365:     }
366: 
367:     public function getHelp() {
368:         return "\nBuilds a php theme file from various @theme tags in all CSS files. \nUsage: themebuild [INPUT DIRECTORY] [OUTPUT FILE]\nOptions: themebuild --keys \n     This will list all the avaliable keys for theming.\n";
369:     }
370: 
371:     // Fun progress bar
372:     public function progressBar($amount) {
373:         echo "\r".($amount*100)."% |";
374:         for ($j = 0; $j < 10; $j++) {
375:             if($j <  $amount * 10) {
376:                 echo '-';
377:             } else {
378:                 echo ' ';
379:             }
380:         }
381:         echo '|';
382: }
383: 
384: }
385: 
386: ?>
387: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0