Hawk - PHP documentation
  • Namespace
  • Class
  • Tree

Namespaces

  • Hawk
    • View
      • Plugins

Classes

  • Hawk\App
  • Hawk\ButtonInput
  • Hawk\Cache
  • Hawk\CheckboxInput
  • Hawk\ColorInput
  • Hawk\Conf
  • Hawk\Controller
  • Hawk\Crypto
  • Hawk\DatabaseSessionHandler
  • Hawk\DatetimeInput
  • Hawk\DB
  • Hawk\DBExample
  • Hawk\DeleteInput
  • Hawk\Dialogbox
  • Hawk\EmailInput
  • Hawk\ErrorHandler
  • Hawk\Event
  • Hawk\FileInput
  • Hawk\FileSystem
  • Hawk\FloatInput
  • Hawk\Form
  • Hawk\FormFieldset
  • Hawk\FormInput
  • Hawk\GenericModel
  • Hawk\GifImage
  • Hawk\HawkApi
  • Hawk\HawkUpdater
  • Hawk\HiddenInput
  • Hawk\HtmlInput
  • Hawk\HTTPRequest
  • Hawk\Icon
  • Hawk\Image
  • Hawk\IntegerInput
  • Hawk\ItemList
  • Hawk\ItemListField
  • Hawk\JpegImage
  • Hawk\Lang
  • Hawk\Language
  • Hawk\LeftSidebarTab
  • Hawk\Less
  • Hawk\Logger
  • Hawk\Mail
  • Hawk\MenuItem
  • Hawk\Model
  • Hawk\NoSidebarTab
  • Hawk\NumberInput
  • Hawk\ObjectInput
  • Hawk\Option
  • Hawk\Panel
  • Hawk\PasswordInput
  • Hawk\Permission
  • Hawk\Plugin
  • Hawk\PluginInstaller
  • Hawk\PngImage
  • Hawk\ProfileQuestion
  • Hawk\ProfileQuestionValue
  • Hawk\RadioInput
  • Hawk\Request
  • Hawk\Response
  • Hawk\RightSidebarTab
  • Hawk\Role
  • Hawk\RolePermission
  • Hawk\Route
  • Hawk\Router
  • Hawk\SelectInput
  • Hawk\Session
  • Hawk\Singleton
  • Hawk\SubmitInput
  • Hawk\Tabs
  • Hawk\TextareaInput
  • Hawk\TextInput
  • Hawk\Theme
  • Hawk\TimeInput
  • Hawk\Upload
  • Hawk\User
  • Hawk\View
  • Hawk\View\Plugins\Accordion
  • Hawk\View\Plugins\Button
  • Hawk\View\Plugins\Form
  • Hawk\View\Plugins\Icon
  • Hawk\View\Plugins\Import
  • Hawk\View\Plugins\Panel
  • Hawk\View\Plugins\Tabs
  • Hawk\View\Plugins\Text
  • Hawk\View\Plugins\Uri
  • Hawk\View\Plugins\Widget
  • Hawk\ViewPlugin
  • Hawk\Widget
  • Hawk\WysiwygInput

Traits

  • Hawk\Utils

Exceptions

  • Hawk\AppStopException
  • Hawk\DBExampleException
  • Hawk\DBException
  • Hawk\FileSystemException
  • Hawk\HawkApiException
  • Hawk\ImageException
  • Hawk\MailException
  • Hawk\UploadException
  • Hawk\ViewException
  1 <?php
  2 /**
  3  * Theme.php
  4  *
  5  * @author  Elvyrra SAS
  6  * @license http://rem.mit-license.org/ MIT
  7  */
  8 
  9 namespace Hawk;
 10 
 11 /**
 12  * This class describes the themes behavior
 13  *
 14  * @package Core
 15  */
 16 class Theme{
 17     /**
 18      * The default theme name
 19      */
 20     const DEFAULT_THEME = 'hawk';
 21 
 22     /**
 23      * The theme css file basename
 24      */
 25     const LESS_BASENAME = 'theme.less';
 26 
 27     /**
 28      * The filename of the compiled Css file
 29      */
 30     const COMPILED_CSS_BASENAME = 'theme.css';
 31 
 32     /**
 33      * The theme css custom file basename
 34      */
 35     const CSS_CUSTOM_BASENAME = 'theme-custom.css';
 36 
 37     /**
 38      * The filename of the definition file
 39      */
 40     const MANIFEST_BASENAME = 'manifest.json';
 41 
 42     /**
 43      * The filename of the preview image
 44      */
 45     const PREVIEW_BASENAME = 'preview.png';
 46 
 47     /**
 48      * The pattern for a theme name
 49      */
 50     const NAME_PATTERN = '[a-zA-Z0-9\-_.]+';
 51 
 52     /**
 53      * The pattern to find the editable variables in the less main file
 54      */
 55     const EDITABLE_VARS_PATTERN = '#^\s*@([\w\-]+)\s*\:\s*(.+?)\s*\;\s*//\s*editable\s*\:\s*"(.+?)"\s*\,?\s*(color|file)?\s*$#m';
 56 
 57     /**
 58      * The theme name
 59      *
 60      * @var string
 61      */
 62     private $name,
 63 
 64     /**
 65      * The theme manifest data
 66      *
 67      * @var array
 68      */
 69     $data,
 70 
 71     /**
 72      * The parent theme name
 73      *
 74      * @var Theme
 75      */
 76     $parent = null;
 77 
 78     /**
 79      * The themes embedded with Hawk, that are not removable
 80      *
 81      * @var array
 82      */
 83     public static $nativeThemes = array('hawk', 'dark');
 84 
 85     /**
 86      * The instanciated themes
 87      *
 88      * @var array
 89      */
 90     private static $themes;
 91 
 92 
 93     /**
 94      * Instanciate a new theme by it name
 95      *
 96      * @param string $name The name of the theme, corresponding to it directory name under /themes
 97      *
 98      * @return Theme The found theme
 99      */
100     public static function get($name = self::DEFAULT_THEME){
101         try{
102             if(!isset($themes[$name])) {
103                 self::$themes[$name] = new self($name);
104             }
105             return self::$themes[$name];
106         }
107         catch(\Exception $e){
108             return null;
109         }
110     }
111 
112 
113     /**
114      * Get the theme configured for the application
115      *
116      * @return Theme The selected theme
117      */
118     public static function getSelected(){
119         return self::get(App::conf()->has('db') ? Option::get('main.selected-theme') : self::DEFAULT_THEME);
120     }
121 
122     /**
123      * Set a theme as the selected one for the application
124      *
125      * @param string $name The name of the theme
126      */
127     public static function setSelected($name){
128         Option::set('main.selected-theme', $name);
129     }
130 
131     /**
132      * Get all the available themes
133      *
134      * @return array All the themes
135      */
136     public static function getAll(){
137         foreach(glob(THEMES_DIR . '*', GLOB_ONLYDIR) as $theme){
138             self::get(basename($theme));
139         }
140 
141         return self::$themes;
142     }
143 
144     /**
145      * Constructor
146      *
147      * @param string $name The theme name
148      */
149     private function __construct($name){
150         $this->name = $name;
151 
152         $this->getDefinition();
153 
154         if(isset($this->data['extends'])) {
155             $this->parent = self::get($this->data['extends']);
156         }
157         elseif($this->name != self::DEFAULT_THEME) {
158             $this->parent = self::get(self::DEFAULT_THEME);
159         }
160     }
161 
162 
163     /**
164      * Get the theme data in the file manifest.json.
165      * If $prop is set, this method returns the property value in the theme data, else it returns all the theme data
166      *
167      * @param string $prop The property in data to get
168      *
169      * @return mixed The value of the property $prop if it is set, else, all the theme data
170      */
171     public function getDefinition($prop = ""){
172         if(!isset($this->data)) {
173             if(!is_file($this->getRootDir() . self::MANIFEST_BASENAME)) {
174                 throw new \Exception('Impossible to get the manifest.json file for the theme '  . $this->name . ' : No such file or directory');
175             }
176             $this->data = json_decode(file_get_contents($this->getRootDir() . self::MANIFEST_BASENAME), true);
177         }
178 
179         if($prop) {
180             return isset($this->data[$prop]) ? $this->data[$prop] : null;
181         }
182         else{
183             return $this->data;
184         }
185     }
186 
187 
188     /**
189      * Get the theme title (data accessible in the manifest.json file of the theme)
190      *
191      * @return string the theme title
192      */
193     public function getTitle(){
194         return $this->getDefinition('title');
195     }
196 
197 
198     /**
199      * Get the theme name (the name of the directory containing the theme)
200      *
201      * @return string The theme name
202      */
203     public function getName(){
204         return $this->name;
205     }
206 
207 
208     /**
209      * Check if the theme is removable. A theme is removable if it's not a native theme, and if it is not the selected one for the application
210      *
211      * @return boolean true if the theme is removable, else false.
212      */
213     public function isRemovable(){
214         return !in_array($this->name, self::$nativeThemes) && self::getSelected() != $this;
215     }
216 
217 
218     /**
219      * Get the start file of the theme. The start file is the file start.php in the theme that initialize special intructions for the theme
220      *
221      * @return string
222      */
223     public function getStartFile(){
224         return $this->getRootDir() . 'start.php';
225     }
226 
227 
228     /**
229      * Get the root directory of the theme files
230      *
231      * @return string The root directory of the theme files
232      */
233     public function getRootDir(){
234         return THEMES_DIR . $this->name . '/';
235     }
236 
237 
238     /**
239      * Get the directory for HTTP accessible files. During theme build, the files are copied in this directory
240      *
241      * @return string
242      */
243     public function getStaticDir(){
244         return STATIC_THEMES_DIR . $this->name . '/';
245     }
246 
247 
248     /**
249      * Get the root URL to get theme files by HTTP request
250      *
251      * @return string
252      */
253     public function getRootUrl(){
254         return THEMES_ROOT_URL . $this->name . '/';
255     }
256 
257 
258     /**
259      * Copy a file from the theme directory to the static directory (to be accessible by HTTP requests) and returns it URL
260      *
261      * @param string $file The source file to get the URL
262      *
263      * @return string The public copied file URL
264      */
265     public function getFileUrl($file){
266         $privateFile = $this->getRootDir() . $file;
267         $publicFile = $this->getStaticDir() . $file;
268 
269         if(!is_file($privateFile)) {
270             throw new \Exception('Impossible to get the URL for the file ' . $privateFile . ' : No such file or directory');
271         }
272 
273         if(!is_file($publicFile) || filemtime($publicFile) < filemtime($privateFile)) {
274             if(!is_dir(dirname($publicFile))) {
275                 mkdir(dirname($publicFile), 0755, true);
276             }
277             App::fs()->copy($privateFile, $publicFile);
278         }
279 
280         return $this->getRootUrl() . $file . '?' . filemtime($publicFile);
281     }
282 
283 
284     /**
285      * Get the file path for the theme preview image
286      *
287      * @return string
288      */
289     public function getPreviewFilename(){
290         return $this->getRootDir() . self::PREVIEW_BASENAME;
291     }
292 
293 
294     /**
295      * Get the URL for the theme preview image
296      *
297      * @return string
298      */
299     public function getPreviewUrl(){
300         return $this->getFileUrl(self::PREVIEW_BASENAME);
301     }
302 
303 
304     /**
305      * Get the dirname containing the less files
306      */
307     public function getLessDirname(){
308         return $this->getRootDir() . 'less/';
309     }
310 
311     /**
312      * Get the base CSS file path
313      *
314      * @return string
315      */
316     public function getBaseLessFile(){
317         return $this->getLessDirname() . self::LESS_BASENAME;
318     }
319 
320 
321     /**
322      * Get the base less file theme.less in static folder
323      *
324      * @return string
325      */
326     public function getStaticLessFile(){
327         return $this->getStaticDir() . 'less/' . self::LESS_BASENAME;
328     }
329 
330 
331     /**
332      * Get the filename of the compiled theme.css file, in the static folder
333      *
334      * @return string The filename
335      */
336     public function getStaticCssFile(){
337         return $this->getStaticDir() . 'less/' . self::COMPILED_CSS_BASENAME;
338     }
339 
340 
341 
342     /**
343      * Build the base css file of the theme, and get the URL of the built file
344      *
345      * @return string The URL of the built CSS file
346      */
347     public function getBaseLessUrl(){
348         $this->build();
349 
350         return $this->getRootUrl() . 'less/' . self::LESS_BASENAME . '?' . filemtime($this->getStaticLessFile());
351     }
352 
353 
354     /**
355      * This method return the URL to access the CSS file of the theme, compiled from theme.less
356      *
357      * @return string The URL of the file theme.css
358      */
359     public function getBaseCssUrl(){
360         $this->build();
361 
362         return $this->getRootUrl() . 'less/' . self::COMPILED_CSS_BASENAME . '?' . filemtime($this->getStaticCssFile());
363     }
364 
365 
366     /**
367      * Build the theme : copy every resource files in themes/{themename}
368      *
369      * @param boolean $force If set to true, the theme will be rebuilt without condition
370      *
371      * @return boole True if the theme has been built, false it it has been taken from cache
372      */
373     public function build($force = false){
374         if($this->getDefinition('extends')) {
375             if(Theme::get($this->getDefinition('extends'))) {
376                 Theme::get($this->getDefinition('extends'))->build($force);
377             }
378         }
379         $build = false;
380         if($force) {
381             $build = true;
382         }
383 
384         if(!file_exists($this->getStaticDir())) {
385             mkdir($this->getStaticDir(), 0755, true);
386             $build = true;
387         }
388 
389 
390         if(!$build) {
391             $dest = $this->getStaticCssFile();
392 
393             if(!is_file($dest)) {
394                 $build = true;
395             }
396             else{
397                 // Get all files in less/
398                 $files = App::fs()->find($this->getLessDirname(), '*.less');
399                 $lastUpdate = filemtime($dest);
400                 foreach($files as $file){
401                     if(filemtime($file) > $lastUpdate) {
402                         $build = true;
403                         break;
404                     }
405                 }
406             }
407         }
408 
409         if($build) {
410             // Build the theme => Copy each accessible files in static dir
411             foreach(glob($this->getRootDir() . '*') as $elt){
412                 if(! in_array(basename($elt), array('views', 'start.php'))) {
413                     App::fs()->copy($elt, $this->getStaticDir());
414                 }
415             }
416 
417             // In the main less file, replace the editable vars by their customized values
418             $values = $this->getVariablesCustomValues();
419             $precompiledLess = preg_replace_callback(
420                 self::EDITABLE_VARS_PATTERN, function ($m) use ($values) {
421                     return '@' . $m[1] . ' : ' . (isset($values[$m[1]]) ? $values[$m[1]] : $m[2]) . ';';
422                 }, file_get_contents($this->getBaseLessFile())
423             );
424 
425             file_put_contents($this->getStaticLessFile(), $precompiledLess);
426 
427             Less::compile($this->getStaticLessFile(), $this->getStaticCssFile());
428         }
429 
430         return $build;
431     }
432 
433 
434     /**
435      * Get the variables in a CSS file content. In CSS files, variables are defined with the folowing format :
436      * /* define("variableName", color|dimension|file, "variable description that will appear in the customization page of the theme", defaultValue) *\/
437      *
438      * @param string $less The Less code to parse
439      *
440      * @return array The variables, where each element contains the 'name' of the variable, it 'type', it 'description', and it 'default' value
441      */
442     public function getEditableVariables($less = null){
443         if(!$less) {
444             $less = file_get_contents($this->getBaseLessFile());
445         }
446         preg_match_all(self::EDITABLE_VARS_PATTERN, $less, $matches, PREG_SET_ORDER);
447         $variables = array();
448         foreach($matches as $match){
449 
450             preg_match_all('#^{a-z}{/a-z}$#', $match[3], $matches_description, PREG_SET_ORDER);
451 
452             App::logger()->error('match=');
453             $description = array();
454             foreach($matches_description as $match_description){
455                 App::logger()->error('match=' . $match_description[1] . "=" . $match_description[2]);
456                 $description[$match_description[1]] = $match_description[2];
457             }
458 
459             $variables[] = array(
460                 'name' => $match[1],
461                 'default' => $match[2],
462                 'description' => $match[3],
463                 'type' => isset($match[4]) ? $match[4] : ''
464             );
465         }
466         return $variables;
467     }
468 
469 
470     /**
471      * Get the customized variables values
472      *
473      * @return array The custom values
474      */
475     public function getVariablesCustomValues(){
476         $options = Option::getPluginOptions('theme-' . $this->name);
477 
478         $values = array();
479         foreach($options as $key => $value){
480             if(preg_match('/^custom\-value\-(.+?)$/', $key, $m)) {
481                 $values[$m[1]] = $value;
482             }
483         }
484 
485         return $values;
486     }
487 
488 
489     /**
490      * Set customized variables values
491      *
492      * @param array $values The values to set
493      */
494     public function setVariablesCustomValues($values){
495         foreach($values as $key => $value){
496             $varname = 'custom-value-' . $key;
497             Option::set('theme-' . $this->name . '.' . $varname, $value);
498         }
499     }
500 
501     /**
502      * Get the directory containing theme userfiles
503      */
504     public function getStaticUserfilesDir(){
505         return $this->getStaticDir() . 'userfiles/';
506     }
507 
508 
509     /**
510      * Get the URL of a static user file
511      *
512      * @param string $filename The basename of the file to get the url
513      *
514      * @return string
515      */
516     public function getStaticUserfilesUrl($filename = ''){
517         return $this->getRootUrl() . 'userfiles/' . $filename;
518     }
519 
520 
521     /**
522      * Get the directory containing the medias uplaoded by the administrator
523      *
524      * @return string The directory containing the medias uploaded by the administrator
525      */
526     public function getMediasDir(){
527         return $this->getStaticUserfilesDir() . 'medias/';
528     }
529 
530     /**
531      * Get the URL of the directory containing the medias uplaoded by the administrator
532      *
533      * @param string $filename The basename of the file to get the URL
534      *
535      * @return string The URL of the directory containing the medias uplaoded by the administrator
536      */
537     public function getMediasUrl($filename = ''){
538         return $this->getStaticUserfilesUrl('medias/' . $filename);
539     }
540 
541 
542     /**
543      * Get the file path of the CSS file customized by the application administrator
544      *
545      *  @return string The file path of the custom CSS file
546      */
547     public function getCustomCssFile(){
548         return $this->getStaticUserfilesDir() . self::CSS_CUSTOM_BASENAME;
549     }
550 
551 
552     /**
553      * Get the URL of the CSS file customized by the application administrator
554      *
555      * @return string The URL of the custom CSS file
556      */
557     public function getCustomCssUrl(){
558         $file = $this->getCustomCssFile();
559         if(!is_dir(dirname($file))) {
560             mkdir(dirname($file), 0775, true);
561         }
562 
563         if(!is_file($file)) {
564             file_put_contents($file, '');
565         }
566 
567         return $this->getStaticUserfilesUrl(self::CSS_CUSTOM_BASENAME);
568     }
569 
570 
571     /**
572      * Get the directory containing the theme views
573      *
574      * @return string The directory containing the theme views files
575      */
576     public function getViewsDir(){
577         return $this->getRootDir() . 'views/';
578     }
579 
580 
581     /**
582      * Get the filename of a view in the theme. If the view file does not exists in the theme, the method will return the view in the default theme
583      *
584      * @param string $filename The basename of the view file, relative to the theme views directory path
585      *
586      * @return string The path of the view file
587      */
588     public function getView($filename){
589         $file = $this->getViewsDir() . $filename;
590         if(!is_file($file) && $this->name != self::DEFAULT_THEME) {
591             if($this->parent) {
592                 // The view does not exists in the theme, and the theme is not the default one, Try to get the view file in the default theme
593                 $file = $this->parent->getView($filename);
594             }
595             else{
596                 $file = self::get(self::DEFAULT_THEME)->getView($filename);
597             }
598         }
599         return $file;
600     }
601 }
Hawk - PHP documentation API documentation generated by ApiGen