Overview

Classes

  • vodka
  • Overview
  • Class
  1: <?php
  2: 
  3: /**
  4: * @version 1.0.1
  5: * @author deseven
  6: * @link https://github.com/deseven/vodka
  7: */
  8: class vodka {
  9: 
 10:     const ver = '1.0.1';
 11:     const rev = 101; // compatibility with older versions
 12: 
 13:     const head        = '{VODKA:HEAD}';
 14:     const canonical   = '{VODKA:CANONICAL}';
 15:     const description = '{VODKA:DESCRIPTION}';
 16:     const keywords    = '{VODKA:KEYWORDS}';
 17:     const menu        = '{VODKA:MENU}';
 18:     const content     = '{VODKA:CONTENT}';
 19:     const title       = '{VODKA:TITLE}';
 20:     const template    = '{VODKA:TEMPLATE}';
 21:     const url         = '{VODKA:URL}';
 22:     const baseurl     = '{VODKA:BASEURL}';
 23:     const name        = '{VODKA:NAME}';
 24:     const cclass      = '{VODKA:CLASS}';
 25: 
 26:     protected $base_url;
 27:     protected $root;
 28:     protected $show_errors = true;
 29:     protected $forbid_scriptname = true;
 30:     protected $clean_unused_vars;
 31:     protected $auto_pages;
 32:     protected $main_page;
 33:     protected $notfound_page;
 34:     protected $pages;
 35:     protected $menu;
 36:     protected $templates;
 37:     protected $aliases;
 38: 
 39:     protected $template;
 40:     protected $content;
 41: 
 42:     protected $current_page;
 43:     protected $current_template;
 44: 
 45:     protected $page_built;
 46:     protected $output;
 47: 
 48:     protected $built_head;
 49:     protected $built_menu;
 50: 
 51:     protected $uri;
 52: 
 53:     protected $replace = [];
 54:     protected $subject = [];
 55: 
 56:     protected $start_time;
 57: 
 58:     private function printError($error,$die = true) {
 59:         if ($this->show_errors) {
 60:             $trace = array_reverse(debug_backtrace());
 61:             echo '<div style="color:white;border:1px solid black;background-color:#990000">[vodka '.$this::ver.'] ERROR - '.$error.'<br>';
 62:             foreach ($trace as $tritem) {
 63:                 echo $tritem['file'].':'.$tritem['line'].'<br>';
 64:             }
 65:             echo '</div>';
 66:         }
 67:         if ($die) {
 68:             header($_SERVER['SERVER_PROTOCOL']." 418 I'm a teapot",true,418);
 69:             exit;
 70:         }
 71:     }
 72: 
 73:     private function _pathinfo($path,$opt = '') {
 74:         $separator = ' qq ';
 75:         $path = preg_replace('/[^ ]/u',$separator."\$0".$separator,$path);
 76:         if ($opt == '') $pathinfo = pathinfo($path);
 77:         else $pathinfo = pathinfo($path,$opt);
 78:         if (is_array($pathinfo)) {
 79:             $pathinfo2 = $pathinfo;
 80:             foreach($pathinfo2 as $key => $val) {
 81:                 $pathinfo[$key] = str_replace($separator,'',$val);
 82:             }
 83:         }
 84:         else if (is_string($pathinfo)) $pathinfo = str_replace($separator,'',$pathinfo);
 85:         return $pathinfo;
 86:     }
 87: 
 88:     /**
 89:     * Engine initialization.
 90:     *
 91:     * *Keep in mind that all internal redirects are handled here.*
 92:     *
 93:     * @param array $params Array with engine parameters. Check `config.php` from demo sources or github wiki to get full list of parameters.
 94:     * @return boolean `true` if everything is ok, `false` otherwise.
 95:     */
 96:     function __construct($params) {
 97:         $this->start_time = microtime(true);
 98: 
 99:         if (!isset($params)) {
100:             $this->printError('no params defined.');
101:             return false;
102:         }
103:         if (!isset($params['system'])) {
104:             $this->printError('no system params defined.');
105:             return false;
106:         }
107:         if (!isset($params['system']['base_url'])) {
108:             $this->printError('base_url is required.');
109:             return false;
110:         } else {
111:             $this->base_url = $params['system']['base_url'];
112:         }
113:         
114:         if (isset($params['system']['show_errors'])) {
115:             $this->show_errors = $params['system']['show_errors'];
116:         }
117:         if (isset($params['system']['clean_unused_vars'])) {
118:             $this->clean_unused_vars = $params['system']['clean_unused_vars'];
119:         }
120:         if ($this->show_errors == true) {
121:             error_reporting(E_ERROR|E_WARNING|E_PARSE); 
122:         } else {
123:             error_reporting(0);
124:         }
125: 
126:         if (isset($params['system']['root'])) {
127:             $this->root = $params['system']['root'];
128:         } else {
129:             $this->root = realpath(dirname(__FILE__));
130:         }
131:         if (isset($params['system']['forbid_scriptname'])) {
132:             $this->forbid_scriptname = $params['system']['forbid_scriptname'];
133:         }
134:         if (isset($params['system']['main_page'])) {
135:             $this->main_page = $params['system']['main_page'];
136:         }
137:         if (isset($params['system']['404_page'])) {
138:             $this->notfound_page = $params['system']['404_page'];
139:         }
140:         if (isset($params['system']['auto_pages'])) {
141:             $this->auto_pages = $params['system']['auto_pages'];
142:         }
143: 
144:         if (isset($params['menu'])) {
145:             $this->menu = $params['menu'];
146:         } elseif (isset($params['menus'])) { // compatibility with an old format
147:             $this->menu = $params['menus'];
148:         }
149: 
150:         if (!isset($params['templates'])) {
151:             $this->printError('no templates defined.');
152:             return false;
153:         }
154:         if ((!isset($params['pages'])) && (!$this->auto_pages)) {
155:             $this->printError('no pages defined.');
156:             return false;
157:         }
158: 
159:         if (isset($params['aliases'])) {
160:             $this->aliases = $params['aliases'];
161:         }
162: 
163:         $this->pages = $params['pages'];
164:         foreach ($this->pages as $key => &$page) {
165:             if (!isset($page['path'])) {
166:                 $this->printError('no path defined for page #'.($key+1).'.');
167:                 return false;
168:             }
169:             if (!isset($page['name'])) {
170:                 $page['name'] = $this->_pathinfo($page['path'],PATHINFO_FILENAME);
171:             }
172:             if (!isset($page['title'])) {
173:                 $page['title'] = $page['name'];
174:             }
175:             if (!isset($page['description'])) {
176:                 $page['description'] = $page['title'];
177:             }
178:             if (!isset($page['keywords'])) {
179:                 $page['keywords'] = "";
180:             }
181:             if (!isset($page['isAJAX'])) {
182:                 $page['isAJAX'] = false;
183:             }
184:         }
185: 
186:         if ($this->auto_pages) {
187:             foreach (glob($this->auto_pages."/*.{html,htm,txt}",GLOB_BRACE) as $file) {
188:                 $add = true;
189:                 foreach ($this->pages as $addedpage) {
190:                     if ($addedpage['path'] == $file) {
191:                         $add = false;
192:                         break;
193:                     }
194:                 }
195:                 if ($add) {
196:                     $this->pages[] = [
197:                         'path' => $file,
198:                         'title' => $this->_pathinfo($file,PATHINFO_FILENAME),
199:                         'name' => $this->_pathinfo($file,PATHINFO_FILENAME)
200:                     ];
201:                 }
202:             }
203:         }
204: 
205:         $this->templates = $params['templates'];
206: 
207:         $this->uri = urldecode($_SERVER['REQUEST_URI']);
208:         $pos = strpos($this->uri,basename($_SERVER['PHP_SELF']));
209:         if ($pos !== false) {
210:             $this->uri = substr_replace($this->uri,'',$pos,strlen(basename($_SERVER['PHP_SELF'])));
211:         }
212:         $pos = strpos($this->uri,dirname($_SERVER['PHP_SELF']));
213:         if ($pos !== false) {
214:             $this->uri = substr_replace($this->uri,'',$pos,strlen(dirname($_SERVER['PHP_SELF'])));
215:         }
216:         $this->uri = preg_replace('~/{2,}~','/',$this->uri);
217:         $this->uri = ltrim($this->uri,'/');
218:         $this->uri = explode('?',$this->uri);
219:         $this->uri = $this->uri[0];
220: 
221:         if ($this->forbid_scriptname) {
222:             if (strpos($_SERVER['REQUEST_URI'],$_SERVER['PHP_SELF']) === 0) {
223:                 $_SERVER['REQUEST_URI'] = str_replace($_SERVER['PHP_SELF'],'',$_SERVER['REQUEST_URI']);
224:                 $_SERVER['REQUEST_URI'] = preg_replace('~/{2,}~','/',$_SERVER['REQUEST_URI']);
225:                 //echo "will redirect to ".rtrim($this->base_url,'/').$_SERVER['REQUEST_URI'];
226:                 header('Location: '.rtrim($this->base_url,'/').$_SERVER['REQUEST_URI'],true,301);
227:                 exit;
228:             }
229:         }
230: 
231:         if ($this->main_page) {
232:             if ($this->uri == $this->main_page) {
233:                 $_SERVER['REQUEST_URI'] = str_replace($_SERVER['PHP_SELF'],'',$_SERVER['REQUEST_URI']);
234:                 $_SERVER['REQUEST_URI'] = preg_replace('~/{2,}~','/',$_SERVER['REQUEST_URI']);
235:                 $_SERVER['REQUEST_URI'] = str_replace($this->main_page,'',$_SERVER['REQUEST_URI']);
236:                 //echo "will redirect to ".rtrim($this->base_url,'/').$_SERVER['REQUEST_URI'];
237:                 header('Location: '.rtrim($this->base_url,'/').$_SERVER['REQUEST_URI'],true,301);
238:                 exit;
239:             }
240:         }
241: 
242:         foreach ($this->pages as &$page) {
243:             if (($page['name'].'/' == $this->uri) || ($page['name'] == $this->uri.'/')) {
244:                 $_SERVER['REQUEST_URI'] = str_replace($this->uri,$page['name'],urldecode($_SERVER['REQUEST_URI']));
245:                 //echo "will redirect to ".$_SERVER['REQUEST_URI'];
246:                 header('Location: '.$_SERVER['REQUEST_URI'],true,301);
247:                 exit;
248:             }
249:         }
250: 
251:         if (isset($_GET)) { // fixing GET
252:             foreach ($_GET as $key => $value) {
253:                 unset($_GET[$key]);
254:                 $key = explode('?',$key);
255:                 if (isset($key[1])) {
256:                     $_GET = [$key[1] => $value] + $_GET;
257:                 }
258:                 break;
259:             }
260:         }
261:     }
262: 
263:     /**
264:     * Return vodka start time.
265:     *
266:     * @return int Number of microseconds.
267:     */
268:     public function getStartTime() {
269:         return $this->start_time;
270:     }
271: 
272:     /**
273:     * Loads template by its name.
274:     *
275:     * @param string $name Name of desired template.
276:     * @return boolean `true` if everything is ok, `false` otherwise.
277:     */
278:     public function loadTemplate($name) {
279:         $this->page_built = false;
280:         if (isset($this->templates[$name])) {
281:             if (file_exists($this->root.'/'.$this->templates[$name].'/template.html')) {
282:                 $this->template = file_get_contents($this->root."/".$this->templates[$name].'/template.html');
283:             } else {
284:                 $this->printError('template `$name` not found.');
285:                 return false;
286:             }
287:         } else {
288:             $this->printError('no such template defined.');
289:             return false;
290:         }
291:         if (dirname($_SERVER['PHP_SELF']) == '/') {
292:             $this->current_template = '/'.$this->templates[$name];
293:         }
294:         else {
295:             $this->current_template = htmlspecialchars(dirname($_SERVER['PHP_SELF'])).'/'.$this->templates[$name];
296:         }
297:         return true;
298:     }
299: 
300:     /**
301:     * Returns page by its name or alias.
302:     *
303:     * @param string $name Name of desired page.
304:     * @return mixed `array` with a page or `false`.
305:     */
306:     public function getPageByName($name) {
307:         if (isset($this->aliases[$name])) {
308:             foreach ($this->pages as $page) {
309:                 if ($page['name'] == $this->aliases[$name]) {
310:                     return $page;
311:                 }
312:             }
313:         }
314:         foreach ($this->pages as $page) {
315:             if ($page['name'] == $name) {
316:                 return $page;
317:             }
318:         }
319:         return false;
320:     }
321: 
322:     /**
323:     * Returns current page.
324:     *
325:     * @return mixed `array` with current page or `false`.
326:     */
327:     public function getCurrentPage() {
328:         if (is_array($this->current_page)) {
329:             return $this->current_page;
330:         }
331:         if (isset($this->aliases[$this->uri])) {
332:             foreach ($this->pages as $page) {
333:                 if ($page['name'] == $this->aliases[$this->uri]) {
334:                     $this->current_page = $page;
335:                     return $page;
336:                 }
337:             }
338:         }
339:         foreach ($this->pages as $page) {
340:             if ($page['name'] == $this->uri) {
341:                 $this->current_page = $page;
342:                 return $page;
343:             }
344:         }
345:         if (!$this->uri) {
346:             if ($this->main_page) {
347:                 foreach ($this->pages as $page) {
348:                     if ($page['name'] == $this->main_page) {
349:                         $this->current_page = $page;
350:                         return $page;
351:                     }
352:                 }
353:             } else {
354:                 $random_page = rand(0,count($this->pages)-1);
355:                 $this->current_page = $this->pages[$random_page];
356:                 return $this->pages[$random_page];
357:             }
358:         }
359:         if ($this->notfound_page) {
360:             foreach ($this->pages as $page) {
361:                 if ($page['name'] == $this->notfound_page) {
362:                     $this->current_page = $page;
363:                     return $page;
364:                 }
365:             }
366:         }
367:         return false;
368:     }
369: 
370:     /**
371:     * Loads current page.
372:     *
373:     * @return boolean `true` if everything is ok, `false` otherwise.
374:     */
375:     public function loadCurrentPage() {
376:         return $this->loadPage($this->getCurrentPage());
377:     }
378: 
379:     /**
380:     * Loads specified page.
381:     *
382:     * @param mixed $page Array with page or page name.
383:     * @return boolean `true` if everything is ok, `false` otherwise.
384:     */
385:     public function loadPage($page) {
386:         $this->content = '';
387:         $this->page_built = false;
388:         if (!is_array($page)) {
389:             $page = $this->getPageByName($page);
390:             if (!is_array($page)) {
391:                 $this->printError('page not defined.');
392:                 return false;
393:             }
394:         }
395:         if (isset($page['path'])) {
396:             if (file_exists($this->root.'/'.$page['path'])) {
397:                 $this->content = file_get_contents($this->root.'/'.$page['path']);
398:                 return true;
399:             } else {
400:                 $this->printError('page not found, check your config.',false);
401:                 return false;
402:             }
403:         } else {
404:             $this->printError('page not defined.');
405:             return false;
406:         }
407:     }
408: 
409:     /**
410:     * Get specified page url.
411:     *
412:     * @param mixed $page Array with page or page name.
413:     * @return mixed `string` with canonical URL of the page or `false`.
414:     */
415:     public function getPageURL($page) {
416:         if (!is_array($page)) {
417:             $page = $this->getPageByName($page);
418:             if (!is_array($page)) {
419:                 $this->printError('page not defined.',false);
420:                 return false;
421:             }
422:         }
423:         return $this->base_url.($page['name'] == $this->main_page ? '' : $page['name']);
424:     }
425: 
426:     /**
427:     * Builds current menu.
428:     *
429:     * @return boolean `true` if everything is ok, `false` otherwise.
430:     */
431:     public function buildCurrentMenu() {
432:         return $this->buildMenu($this->getCurrentPage());
433:     }
434: 
435:     /**
436:     * Builds menu for specified page.
437:     *
438:     * @param mixed $page Array with page or page name.
439:     * @return boolean `true` if everything is ok, `false` otherwise.
440:     */
441:     public function buildMenu($page) {
442:         if (!is_array($this->menu)) {
443:             return false;
444:         }
445:         if (!is_array($page)) {
446:             $page = $this->getPageByName($page);
447:             if (!is_array($page)) {
448:                 $this->printError('page not defined.');
449:                 return false;
450:             }
451:         }
452:         for ($i = 0;$i < count($this->pages);$i++) {
453:             $cur_item = $this->menu['html'];
454:             $visible = true;
455:             if (isset($this->pages[$i]['visible'])) {
456:                 $visible = $this->pages[$i]['visible'];
457:             }
458:             if ($visible) {
459:                 if (($this->pages[$i]['name'] == $page['name']) && isset($this->menu['selected_class'])) {
460:                     $replace = $this->menu['selected_class'];
461:                     if (($i == 0) && (isset($this->menu['selected_class_first']))) {
462:                         $replace .= ' '.$this->menu['selected_class_first'];
463:                     }
464:                     if (($i == count($this->pages) - 1) && (isset($this->menu['selected_class_last']))) {
465:                         $replace .= ' '.$this->menu['selected_class_last'];    
466:                     }
467:                     $cur_item = str_replace($this::cclass,$replace,$cur_item);
468:                 } else {
469:                     $cur_item = str_replace($this::cclass,'',$cur_item);
470:                 }
471:                 $cur_item = str_replace($this::name,$this->pages[$i]['name'],$cur_item);
472: 
473:                 if (dirname($_SERVER['PHP_SELF']) != '/') {
474:                     $cur_item = str_replace($this::url,htmlspecialchars(dirname($_SERVER['PHP_SELF'])).'/'.($this->pages[$i]['name'] == $this->main_page ? '' : $this->pages[$i]['name']),$cur_item);
475:                 } else {
476:                     $cur_item = str_replace($this::url,'/'.($this->pages[$i]['name'] == $this->main_page ? '' : $this->pages[$i]['name']),$cur_item);
477:                 }
478:                 $cur_item = str_replace($this::title,$this->pages[$i]['title'],$cur_item);
479:                 $this->built_menu .= $cur_item;
480:             }
481:         }
482:         return true;
483:     }
484: 
485:     /**
486:     * Builds current page.
487:     *
488:     * @return boolean `true` if everything is ok, `false` otherwise.
489:     */
490:     public function buildCurrentPage() {
491:         return $this->buildPage($this->getCurrentPage());
492:     }
493: 
494:     /**
495:     * Builds specified page.
496:     *
497:     * @param mixed $page Array with page or page name.
498:     * @return boolean `true` if everything is ok, `false` otherwise.
499:     */
500:     public function buildPage($page) {
501:         if (!is_array($page)) {
502:             $page = $this->getPageByName($page);
503:             if (!is_array($page)) {
504:                 $this->printError('page not defined.');
505:                 return false;
506:             }
507:         }
508:         if ($page['isAJAX']) {
509:             $this->output = $this::content;
510:         } else {
511:             $this->output = $this->template;
512:         }
513:         if (isset($page['custom'])) {
514:             $page['custom'] = array_reverse($page['custom']);
515:             while ($custom = current($page['custom'])) {
516:                 $var = key($page['custom']);
517:                 if (substr($var,0,1) != '{') {
518:                     $var = '{'.$var;
519:                 }
520:                 if (substr($var,-1) != '}') {
521:                     $var .= "}";
522:                 }
523:                 array_unshift($this->replace,$var);
524:                 array_unshift($this->subject,$page['custom'][key($page['custom'])]);
525:                 next($page['custom']);
526:             }
527:         }
528:         array_unshift(
529:             $this->replace,
530:             $this::content,
531:             $this::head,
532:             $this::canonical,
533:             $this::description,
534:             $this::keywords,
535:             $this::template,
536:             $this::title,
537:             $this::menu,
538:             $this::baseurl
539:         );
540:         array_unshift(
541:             $this->subject,
542:             $this->content,
543:             $this->built_head,
544:             $this->base_url.($page['name'] == $this->main_page ? '' : $page['name']),
545:             $page['description'],
546:             $page['keywords'],
547:             $this->current_template,
548:             $page['title'],
549:             $this->built_menu,
550:             $this->base_url
551:         );
552:         $this->output = str_replace($this->replace,$this->subject,$this->output);
553:         if ($this->clean_unused_vars) {
554:             $this->output = preg_replace('/{[A-Z0-9:]+}/','',$this->output);
555:         }
556:         if ($page['name'] == $this->notfound_page) {
557:             header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found',true,404);
558:         }
559:         $this->page_built = true;
560:         echo $this->output;
561:         return true;
562:     }
563: 
564:     /**
565:     * Replaces variable (string encapsulated in curly braces) in page.
566:     * 
567:     * *Please note that this function doesn't modify the output directly, call `buildPage()` to apply your actions.*
568:     *
569:     * @param string $var
570:     * @param string $string
571:     * @return boolean Always `true`.
572:     * @example `$vodka->replaceVar('VAR','value');`
573:     */
574:     public function replaceVar($var,$string = null) {
575:         if (substr($var,0,1) != '{') {
576:             $var = '{'.$var;
577:         }
578:         if (substr($var,-1) != '}') {
579:             $var .= "}";
580:         }
581:         $this->replace[] = $var;
582:         $this->subject[] = $string;
583:         return true;
584:     }
585: 
586:     /**
587:     * Appends $string to the `{VODKA:HEAD}` part of the template.
588:     * 
589:     * *Please note that this function doesn't modify the output directly, call `buildPage()` to apply your actions.*
590:     *
591:     * @param string $string
592:     * @return boolean Always `true`.
593:     * @example `$vodka->appendHead('<meta name="robots" content="all">');`
594:     */
595:     public function appendHead($string) {
596:         $this->built_head .= $string;
597:         return true;
598:     }
599: 
600: }
601: 
602: ?>
API documentation generated by ApiGen