source: ef.php

Last change on this file was 334402c, checked in by Pawel Foremski <pjf@…>, 11 years ago

Initial commit

  • Property mode set to 100644
File size: 26.9 KB
Line 
1<?php
2/* ef - an Execution Framework for PHP applications
3 *
4 * Copyright (C) 2009-2010 ASN Sp. z o.o.
5 * Author: Pawel Foremski <pjf@asn.pl>
6 *
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free Software
9 * Foundation; either version 3 of the License, or (at your option) any later
10 * version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
14 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * this program; if not, see <http://www.gnu.org/licenses>.
18 */
19
20/*
21 * CONSTANTS
22 */
23define("EF_ACTION",   "action.php");
24define("EF_BLOCK",    '/^block-(.*)\.php$/');
25define("EF_HOOKS",    "/hooks");
26define("EF_LANG",     "lang.*.php");
27define("REGEXP_LANG", '/^[a-z]{2}$/');
28
29/*
30 * action.php return codes
31 */
32/** carry on */
33define("EF_CONT",  1);
34
35/** dont output anything */
36define("EF_ABRT", -1);
37
38/** stop here */
39define("EF_STOP", -2);
40
41/*
42 * GLOBALS: Default values of ef settings
43 */
44$ef_dir              = "/usr/share/php/ef"; /**< path to ef applications */
45$ef_ctl              = getcwd() . "/..";    /**< main application root dir */
46$ef_logs             = array();             /**< log messages go here */
47$ef_log_level        = 1;                   /**< maximum log level to log a message */
48$ef_log_to_stdout    = FALSE;               /**< to log using "echo" or not */
49$ef_log_indent_width = 1;                   /**< log indentation width */
50$ef_hooks            = array();             /**< hooks are registered here */
51$ef_trans            = array();             /**< contains translations */
52$ef_default_lang     = "en";                /**< default user language */
53$ef_lang             = NULL;                /**< current language */
54$ef_default_action   = "/index";            /**< default action to run */
55$ef_cli_mode         = (php_sapi_name() == "cli");    /**< are we running in CLI environment? */
56$ef_span             = "span";              /**< HTML tag to use for CSS integration */
57$ef_reqdefs          = array();             /**< default efreq values #5 */
58
59/** Global current request state
60 *
61 * Used:
62 * - for simpler ef_*() in action.php
63 * - as ef_run() output data format
64 * - as ef_tpl() input data format
65 *
66 * Members:
67 * - path - path being handled
68 * - appname - application name (ef_rootname() on path above)
69 * - curpath - current position in path
70 * - vars - variable scope
71 * - blocks - template blocks
72 * - retcode - last action.php return code
73 * - cwd - for better cwd (Current Working Dir) tracking
74 */
75$ef_state = array(
76        "path"     => FALSE,
77        "appname"  => "none",
78        "curpath"  => "",
79        "vars"     => array(),
80        "blocks"   => array(),
81        "retcode"  => EF_ABRT,
82        "cwd"      => getcwd()
83);
84
85/** Helper for exports in ef_include_extract() #4 */
86$ef_export_helper = array();
87
88
89/*
90 * LOGGING
91 */
92
93/** Logs $msg with given log $level
94 * The lower is $level,
95 */
96function ef_log($level, $msg)
97{
98        global $ef_log_to_stdout;
99        global $ef_log_level;
100        global $ef_logs;
101        global $ef_state;
102
103        if ($level > $ef_log_level) return;
104
105        if (!$GLOBALS["ef_cli_mode"]) {
106                if      ($level < 2) $msg = "<b><u>$msg</u></b>";
107                else if ($level < 4) $msg = "<b>$msg</b>";
108                else if ($level < 6) $msg = "<b><u>!!!</u></b> $msg";
109        }
110
111        /* add $ef_state info */
112        $msg = "[$ef_state[curpath]] $msg";
113
114        if ($ef_log_to_stdout) echo $msg . "\n";
115        $ef_logs[] = $msg;
116}
117
118/** Prints backtrace, logs, and dies */
119function ef_die($msg = "(no message)", $http = "HTTP/1.0 400 Bad Request")
120{
121        ef_obabort();
122
123        if ($GLOBALS["ef_cli_mode"]) die($msg);
124
125        @header($http);
126
127        echo "<b>FATAL ERROR:</b> <i>";
128        if (is_array($msg) || is_object($msg))
129                echo "<pre>" . print_r($msg, 1) . "</pre>";
130        else
131                echo $msg;
132        echo "</i><br />\n";
133
134        if (EF_DEBUG !== true)
135                die();
136
137        echo "<pre>";
138        echo "<b>BACKTRACE</b>\n";
139
140        $bt = debug_backtrace();
141        foreach ($bt as $level) {
142                echo "<b>$level[function](";
143                if ($level["args"] && count($level["args"]) == 1) echo "</b>" . $level["args"][0] . "<b>";
144                echo ")</b> from <i>$level[file]:$level[line]</i>\n";
145                if ($level["args"] && count($level["args"]) > 1) echo print_r($level["args"], 1);
146        }
147
148        echo "\n<b>EF LOGS</b>\n";
149        echo print_r($GLOBALS["ef_logs"], 1);
150
151        echo "\n<b>EF STATE DUMP</b>\n";
152        echo print_r($GLOBALS["ef_state"], 1);
153        echo "</pre>";
154
155        die();
156}
157
158/*
159 * TRANSLATIONS
160 */
161
162/** Include a language file
163 *
164 * The $format should look like: 'lang.*.php', where '*' sign will be
165 * replaced with $lang. If including of such file fails, we fallback on English
166 * ("en" language).
167 *
168 * @param  $format    include file format
169 * @param  $lang      preferred language
170 * @retval true       preferred or default language was loaded
171 * @retval false      nothing was loaded
172 *
173 * @note watch for get_defined_vars() at the end
174 */
175function ef_include_lang($format, $lang = FALSE)
176{
177        global $ef_lang;
178
179        ef_log(10, "ef_include_lang($format, [$lang])");
180
181        if ($lang === FALSE)
182                $lang = $ef_lang;
183
184        $pref = str_replace("*", $lang, $format);
185        $engl = str_replace("*", "en", $format);
186
187        /* XXX: don't remove these braces (required by includes) */
188        if (file_exists($pref)) {
189                ef_log(6, "inclang: include_once $pref");
190                include_once $pref;
191        }
192        else if (file_exists($engl)) { /* fallback on English */
193                ef_log(6, "inclang: include_once $engl");
194                include_once $engl;
195        }
196        else {
197                ef_log(10, "inclang: could not find any matching files");
198                return FALSE;
199        }
200
201        /* import any defined arrays into global $ef_trans */
202        foreach (get_defined_vars() as $var) {
203                if (is_array($var))
204                        $GLOBALS["ef_trans"] = array_merge($GLOBALS["ef_trans"], $var);
205        }
206
207        return TRUE;
208}
209
210/** Translate sentence using files loaded by ef_include_lang()
211 *
212 * @param $what      sentence to translate
213 * @return           string: translation of $what
214 * @note             name of the function is short to make it handy
215 */
216function t($what)
217{
218        if (isset($GLOBALS["ef_trans"][$what]))
219                return $GLOBALS["ef_trans"][$what];
220        else
221                return $what;
222}
223
224/** Saves user language in a cookie
225 *
226 * @param $lang   the language to save as the preferred one
227 * @return bool
228 * @retval true   language saved sucessfully
229 * @retval false  there was an error
230 */
231function ef_save_lang($lang)
232{
233        global $ef_lang;
234
235        ef_log(3, "saving user language ($lang)");
236
237        if ($GLOBALS["ef_cli_mode"]) {
238                ef_log(1, "huh? we're in CLI mode, dummy! :)");
239                return FALSE;
240        }
241
242        if (headers_sent()) {
243                ef_log(2, "can't save language in cookie - headers already sent");
244                return FALSE;
245        }
246
247        /* save user language in a cookie, for next 10 years ;-)
248         * XXX: the path (/) */
249        if (!setcookie("ef_lang", $lang, time() + 3600 * 24 * 3650, "/")) {
250                /* something failed */
251                return FALSE;
252        }
253
254        $ef_lang = $lang;
255        return TRUE;
256}
257
258/*
259 * HOOKS
260 */
261
262/** Reads hooks file and merges it's contents with global $ef_hooks array
263 * @param $path path to file
264 */
265function ef_read_hooks_file($path)
266{
267        include_once $path;
268
269        ef_log(3, "ef_read_hooks_file($path)");
270
271        foreach (get_defined_vars() as $var) {
272                if (!is_array($var)) continue;
273
274                foreach ($var as $key => $hook) {
275                        if (!isset($GLOBALS["ef_hooks"][$key]))
276                                $GLOBALS["ef_hooks"][$key] = array();
277                        if (is_array($hook))
278                                $GLOBALS["ef_hooks"][$key] =
279                                        array_merge($GLOBALS["ef_hooks"][$key], $hook);
280                        else
281                                $GLOBALS["ef_hooks"][$key] =
282                                        array_merge($GLOBALS["ef_hooks"][$key], array($hook));
283                }
284        }
285}
286
287/** Recursively reads hooks from $dir
288 * File names should be ended with ".php" extension.
289 */
290function ef_read_hooks($dir)
291{
292        if (!is_dir($dir)) return;
293
294        foreach (scandir($dir) as $node) {
295                if ($node[0] == '.') continue;
296
297                if (is_dir($node))
298                        ef_read_hooks($node);
299                else if (substr($node, -4) == ".php")
300                        ef_read_hooks_file("$dir/$node");
301        }
302}
303
304/*
305 * TOOLS
306 */
307
308/** Switch to new ef state
309 *
310 * @see $ef_state
311 * @return   old state (suitable for ef_state_restore())
312 */
313function ef_state_new($path, $appname, &$curpath, &$vars, &$retcode)
314{
315        global $ef_state;
316
317        /* copy current state */
318        $state = array();
319        $state = $ef_state;
320
321        $ef_state = array();
322        $ef_state["path"]    = $path;
323        $ef_state["appname"] = $appname;
324        $ef_state["curpath"] = &$curpath;
325        $ef_state["vars"]    = &$vars;
326        $ef_state["retcode"] = &$retcode;
327
328        $ef_state["blocks"]  = array(); /* XXX: new */
329
330        ef_chdir($GLOBALS["ef_dir"]);
331
332        return $state;
333}
334
335/** Switch to previous ef state
336 *
337 * @param $state    new state
338 * @return          previous state (suitable for ef_state_restore())
339 */
340function ef_state_restore($state)
341{
342        global $ef_state;
343
344        $oldstate = $ef_state;
345
346        $ef_state = $state;
347        chdir($ef_state["cwd"]); /* bit hacky */
348
349        return $oldstate;
350}
351
352/** Returns element at path beginning - like basename(), but inverse
353 *
354 * @note example: assert(ef_rootname("/foo/bar/dirboo/x.php" == "foo"));
355 * @param $path     path to operate on
356 * @retval false: if it couldn't find the rootname
357 * @retval string: in case it was possible
358 */
359function ef_rootname($path)
360{
361        $matches = array();
362        preg_match(';^[/]+([^/]+)(/|$);', $path, $matches);
363
364        if (!isset($matches[1]))
365                return FALSE;
366        else
367                return $matches[1];
368}
369
370/** Generates the BASE HTML element
371 * @note   in HTML, put the returned string &lt;head>&lt;base href=" HERE ">&lt;/head>
372 */
373function ef_htmlbase()
374{
375        $url = "";
376
377        if ($_SERVER["HTTPS"] == "on")
378                $url .= "https://";
379        else
380                $url .= "http://";
381
382        $url .= $_SERVER["HTTP_HOST"];
383        $url .= $_SERVER["SCRIPT_NAME"];
384
385        return $url;
386}
387
388/** realpath() with variable root dir
389 *
390 * In other words, it translates all "./", "../", etc. in given $path and
391 * returns the result relative to given $root directory.
392 *
393 * We don't use realpath() here, because it checks whether checked path is valid
394 * (exists) - we just want pure relative->absolute translation.
395 *
396 * Examples:
397 * - Absolute $path:
398 *   - ef_realpath("/foo/bar", "/home/root") -> "/foo/bar"
399 *   - ef_realpath("/", "/home/root") -> "/"
400 * - Current working dir "/blah/blablah/dir1/dir2":
401 *   - ef_realpath(".", "/blah/blablah") -> "/dir1/dir2"
402 *   - ef_realpath("/", "/blah/blablah") -> "/"
403 *   - ef_realpath("../", "/blah/blablah") -> "/dir1"
404 *   - ef_realpath("./dir3/../../dir1", "/blah/blablah") -> "/dir1"
405 *
406 * As you see, it's mainly useful when $path is relative.
407 *
408 * @param $path  path to translate
409 * @param $root  virtual root directory; if FALSE, then $ef_dir used (default)
410 * @param $cwd   optionally override output of ef_getcwd() function
411 */
412function ef_realpath($path, $root = FALSE, $cwd = FALSE)
413{
414        ef_log(12, "ef_realpath($path, $root, $cwd)");
415
416        if ($path[0] == '/') {
417                $current = "";
418        }
419        else {
420                if ($root === FALSE)
421                        $root = $GLOBALS["ef_dir"];
422                if ($root == "/")
423                        $root = "";
424
425                /* start in current working directory, as path is given in relative form */
426                if ($cwd === FALSE)
427                        $current = ef_getcwd();
428                else
429                        $current = $cwd;
430
431                if (substr($current, 0, strlen($root)) != $root) {
432                        /* we're out of our root */
433                        $current = "";
434                }
435                else {
436                        /* remove $root (usually a "system prefix") from path beginning - we want the
437                         * result to be relative to $root */
438                        $current = substr($current, strlen($root));
439                }
440        }
441
442        /* trivial */
443        foreach (explode("/", $path) as $part) {
444                switch ($part) {
445                        case "":
446                        case ".":
447                                continue;
448                                break;
449                        case "..":
450                                $current = dirname($current);
451                                break;
452                        default:
453                                $current .= "/$part";
454                                break;
455                }
456        }
457
458        /* all dirs except root dir doesn't end with "/" */
459        if ($current == "") $current = "/";
460
461        /* hacky */
462        if (substr($current, 0, 2) == "//")
463                $current = substr($current, 1);
464
465        ef_log(12, "ef_realpath(): result: $current");
466        return $current;
467}
468
469/** A chdir which won't cause symbolic links to be resolved in getcwd()
470 *
471 * @return old value of ef_getcwd()
472 */
473function ef_chdir($path)
474{
475        global $ef_state;
476        $ret = $ef_state["cwd"];
477
478        if ($path[0] != "/")
479                $ef_state["cwd"] .= "/$path";
480        else
481                $ef_state["cwd"] = $path;
482
483        chdir($path);
484        return $ret;
485}
486
487/** A getcwd() replacement which doesn't resolve symbolic links */
488function ef_getcwd()
489{
490        return $GLOBALS["ef_state"]["cwd"];
491}
492
493/** Retrieves value from $req with optional default value */
494function ef_getreq($name, $default = NULL)
495{
496        global $ef_state;
497
498        if (isset($ef_state["vars"]["req"][$name]))
499                return $ef_state["vars"]["req"][$name];
500        else
501                return $default;
502}
503
504/** Retrieves value from $_GET with optional default value */
505function ef_getget($name, $default = NULL)
506{
507        return (isset($_GET[$name]) ? $_GET[$name] : $default);
508}
509
510/** Make a CSS-suitable shortcut of given path
511 *
512 * E.g. /jinks/content/view/single/post -> jcvs-post
513 *
514 * @param $path   path to shorten
515 * @return        string: shortcut
516 */
517function ef_path_shortcut($path) /* #2 */
518{
519        foreach (explode('/', $path) as $part) {
520                if (!$part) continue;
521
522                if ($prev) $ret .= $prev[0];
523                $prev = $part;
524        }
525
526        return ($ret) ? "$ret-$prev" : "$prev";
527}
528
529/*
530 * Template support
531 */
532
533/** Returns true if given block is bound */
534function ef_block_exists($name)
535{
536        return (
537                isset($GLOBALS["ef_state"]["blocks"][$name])
538        );
539}
540
541/** Returns true if given block is bound as a link */
542function ef_block_islink($name)
543{
544        return (
545                ef_block_exists($name) &&
546                $GLOBALS["ef_state"]["blocks"][$name]["type"] == "link"
547        );
548}
549
550/** Flushes block bindings - all or single, if block $name is given */
551function ef_block_clean($name = false)
552{
553        if ($name)
554                unset($GLOBALS["ef_state"]["blocks"][$name]);
555        else
556                $GLOBALS["ef_state"]["blocks"] = array();
557}
558
559/** Register a block->file binding
560 *
561 * @param $name   block name
562 * @param $path   file path (relative)
563 */
564function ef_block_include($name, $path)
565{
566        global $ef_state;
567
568        /* don't override links */
569        if (ef_block_islink($name)) return;
570
571        $ef_state["blocks"][$name] = array(
572                "type"    => "file",
573                "curpath" => $ef_state["curpath"],
574                "cwd"     => ef_getcwd(),
575                "file"    => $GLOBALS["ef_dir"] . ef_realpath($path)
576        );
577}
578
579/** Register a block->HTML binding
580 *
581 * @param $name  block name
582 * @param $html  HTML code
583 */
584function ef_block_write($name, $html)
585{
586        global $ef_state;
587
588        /* don't override links */
589        if (ef_block_islink($name)) return;
590
591        $ef_state["blocks"][$name] = array(
592                "type"    => "html",
593                "curpath" => $ef_state["curpath"],
594                "html"    => $html
595        );
596}
597
598/** Register a block->link binding
599 *
600 * All further file and HTML bindings for this block will be ignored. On drawing, block $newname
601 * will be taken instead of $name.
602 *
603 * @param $name     name of block to be masked
604 * @param $newname  name of block replacing it
605 */
606function ef_block_mv($name, $newname)
607{
608        global $ef_state;
609
610        $ef_state["blocks"][$name] = array(
611                "type"    => "link",
612                "curpath" => $ef_state["curpath"],
613                "link"    => $newname
614        );
615}
616
617/** Draw given block from current ef state
618 *
619 * @param $name   name of block
620 * @param $args   additional data to $out
621 * @note  if CSS support is on, a generated code will be wrapped in a HTML span tag with
622 *        class name derived from efpath (see #2)
623 */
624function ef_block($name, $args = array())
625{
626        global $ef_state;
627        global $ef_span;
628
629        ef_log(10, "ef_block($name)");
630
631        if (!ef_block_exists($name)) {
632                ef_log(3, "ef_block(): '$name' not defined");
633                return;
634        }
635
636        $block = $ef_state["blocks"][$name];
637
638        if ($ef_state["css"]) {
639                if (EF_DEBUG === true)
640                        echo "<!-- ef_block $name: $ef_state[curpath] START //-->\n";
641
642                if ($block["curpath"]) /* #2 */
643                        echo "<$ef_span class=\"" . ef_path_shortcut($block["curpath"]) . "\">\n";
644        }
645
646        if (is_array($args)) { /* restored #3 */
647                $oldout = $ef_state["vars"]["out"];
648                $ef_state["vars"]["out"] = array_merge($ef_state["vars"]["out"], $args);
649        }
650
651        /* handle different kinds of block bindings */
652        switch ($block["type"]) {
653                case "file":
654                        $startdir = ef_chdir($block["cwd"]);
655                        ef_include_extract($block["file"], $ef_state["vars"]["out"]);
656                        ef_chdir($startdir);
657                        break;
658                case "html":
659                        echo $block["html"];
660                        break;
661                case "link":
662                        ef_block($block["link"]); /* XXX: $args already merged in ef state */
663                        break;
664                default:
665                        ef_log(1, "ef_block(): unknown block type: '$block[type]'");
666                        break;
667        }
668
669        if (is_array($args)) /* #3 */
670                $ef_state["vars"]["out"] = $oldout;
671
672        if ($ef_state["css"]) {
673                echo "</$ef_span>\n";
674                if (EF_DEBUG === true)
675                        echo "<!-- ef_block $name: $ef_state[curpath] STOP //-->\n";
676        }
677}
678
679/* convenience */
680function ef_obstart() { ob_start(); }
681function ef_obstop()  { $str = ob_get_contents(); ob_end_clean(); return $str; }
682function ef_obabort() { ob_end_clean(); }
683
684/*
685 * REQUEST HELPERS
686 */
687
688/** include() which supports separate variable scope
689 * @param $_file     file to include
690 * @param $_vars     variable scope, passed by reference
691 * @param $_override variables to override in $vars (for convenience)
692 * @return return code passed from included file
693 */
694function ef_include_extract($_file, &$_vars, $_override = array())
695{
696        if (is_array($_vars))
697                extract($_vars, EXTR_OVERWRITE | EXTR_REFS);
698        if (is_array($_override))
699                extract($_override, EXTR_OVERWRITE);
700
701        /* XXX: the idea behind export helper is that after single action.php finishes we need to
702         *      decide on what to do with the variables it created; by default, they're deleted but if
703         *      the programmer "tags" a variable using ef_globals() or ef_outputs(), then its value will
704         *      be carried on and used later (#4)
705         * XXX: $GLOBALS["ef_export_helper"] is implemented as a stack to let for ef_include_extract()
706         *      nesting */
707        array_push($GLOBALS["ef_export_helper"], array(
708                "globals" => array(), "outputs" => array()
709        ));
710
711        $_retcode = include $_file;
712
713        $_exports = array_pop($GLOBALS["ef_export_helper"]);
714
715        foreach ($_exports["globals"] as $_varname => $_foo) {
716                $_vars[$_varname] = $$_varname;
717        }
718
719        /* XXX: all template variables are also globals */
720        foreach ($_exports["outputs"] as $_varname => $_foo) {
721                $_vars[$_varname] = $$_varname;
722                $_vars["out"][$_varname] = &$_vars[$_varname];
723        }
724
725        return $_retcode;
726}
727
728/** Mark variables to be exported either as globals or template variables
729 * @param $where   either "globals" or "outputs"
730 * @param $what    array with variable names
731 */
732function _ef_export($where, &$what) /* #4 */
733{
734        $i = count($GLOBALS["ef_export_helper"]);
735        $a = &$GLOBALS["ef_export_helper"][$i-1]; /* append to last element on "state stack" */
736        $a[$where] = array_merge($a[$where], array_flip($what));
737}
738function ef_globals() { $a = func_get_args(); _ef_export("globals", $a); }
739function ef_outputs() { $a = func_get_args(); _ef_export("outputs", $a); }
740
741/*
742 * REQUEST HANDLERS
743 */
744
745/** The ef
746 *
747 * @param $path      efpath
748 * @param $req       request parameters
749 * @return           bool: true on success, false on failure
750 */
751function ef_run($path, $req = array())
752{
753        ef_log(3, "ef_run($path)");
754        if (count($req)) ef_log(6, "\$req contents: " . print_r($req, 1));
755
756        $path     = ef_realpath($path);   /* request path */
757        $appname  = ef_rootname($path);   /* application name, first part of $action */
758        $curpath  = "";                   /* current depth of $path */
759        $retcode  = EF_CONT;              /* last return code of action.php */
760
761        if (!is_dir("$GLOBALS[ef_dir]/$path")) {
762                ef_log(1, "invalid action: $path");
763                return false;
764        }
765
766        /* apply default $req values #5 */
767        if (isset($GLOBALS["ef_reqdefs"][$appname]))
768                $req = array_merge($GLOBALS["ef_reqdefs"][$appname], $req);
769
770        /* XXX: create new variable scope with fundamental variables */
771        $vars = array(
772                "efpath" => $path,
773                "efapp"  => $appname,
774                "ef_dir" => $GLOBALS["ef_dir"],
775                "ef_ctl" => $GLOBALS["ef_ctl"],
776                "req"    => $req,
777                "out"    => array()    /* template variable scope */
778        );
779
780        /* start new state */
781        $oldstate = ef_state_new($path, $appname, $curpath, $vars, $retcode);
782
783        ef_obstart();
784        foreach (explode("/", $path) as $nextdir) {
785                if (!$nextdir) continue;
786
787                /* go one dir deeper */
788                ef_chdir($nextdir);
789                $curpath .= "/$nextdir";
790                ef_log(10, "ef_run(): at " . ef_getcwd() . ", curpath=$curpath");
791
792                /* auto-include lang.*.php */
793                ef_include_lang(EF_LANG);
794
795                /* create list of scripts to run */
796                $todo = array("$curpath/" . EF_ACTION);     /* start with just action.php */
797                if (isset($GLOBALS["ef_hooks"][$curpath]))  /* and add all hooks */
798                        $todo = array_merge($todo, $GLOBALS["ef_hooks"][$curpath]);
799
800                /* automatically register block-*.php files */
801                $matches = array();
802                foreach (scandir(".") as $blockfile) {
803                        if (preg_match(EF_BLOCK, $blockfile, $matches))
804                                ef_block_include($matches[1], $matches[0]); /* XXX: stores some $ef_state vars */
805                }
806
807                /* run action.php scripts */
808                foreach ($todo as $filepath) {
809                        $file = $GLOBALS["ef_dir"] . $filepath;
810                        if (file_exists($file)) {
811                                $retcode = ef_include_extract($file, $vars);
812
813                                if ($retcode < EF_CONT) {
814                                        ef_log(5, "ef_run($path): breaking @ $curpath ($retcode)");
815                                        break 2;
816                                }
817                        }
818                }
819        } /* action.php loop */
820
821        /* log action.php's output */
822        $actionoutput = ef_obstop();
823        if ($actionoutput)
824                ef_log(1, "ef_run($path): stdout: $actionoutput");
825
826        ef_log(11, "ef_run($path): out: " . print_r($vars["out"], 1));
827
828        return ef_state_restore($oldstate);
829}
830
831/** Generate HTML output out of passed data
832 *
833 * @param $state     data generated by ef_run()
834 * @param $css       if not empty, turn on CSS support:
835 *                   -# wrap generated HTML in span tag with class="ef-$appname"
836 *                   -# if $css[class] is set, add this CSS class to 1. and set $out[efcssclass]
837 *                   -# if $css[id] is set, add an id= to this span tag and set $out[efcssid]
838 *                   $css may be just a boolean "true" (default)
839 * @return           HTML output as string
840 */
841function ef_tpl($state, $css = true)
842{
843        global $ef_state;
844        global $ef_span;
845
846        if (!is_array($state)) {
847                ef_log(1, "ef_tpl: passed \$state is not an array");
848                return false;
849        }
850
851        if ($state["retcode"] == EF_ABRT) {
852                ef_log(1, "ef_tpl: $state[path]: can't draw - action aborted");
853                return false;
854        }
855
856        $oldstate = ef_state_restore($state);
857        $ef_state["css"] = &$css;
858
859        ef_obstart();
860        if ($css) {
861                if (EF_DEBUG === true && EF_TPL_DEBUG === true)
862                        echo "<!-- ef_tpl: path=$ef_state[path] START //-->\n";
863
864                echo "<$ef_span class=\"ef-$ef_state[appname]";
865                if (is_array($css)) {
866                        if (isset($css["class"])) echo " $css[class]";
867                        echo "\"";
868                        if (isset($css["id"])) echo " id=\"$css[id]\"";
869                        echo ">\n";
870                }
871                else echo "\">\n";
872
873                $ef_state["vars"]["out"]["efcssclass"] = $css["class"];
874                $ef_state["vars"]["out"]["efcssid"] = $css["id"];
875        }
876
877        ef_block("main", $css);
878
879        if ($css) {
880                echo "</$ef_span>\n";
881                if (EF_DEBUG === true && EF_TPL_DEBUG === true)
882                        echo "<!-- ef_tpl: path=$ef_state[path] STOP //-->\n";
883        }
884
885        $ret = ef_obstop();
886        ef_state_restore($oldstate);
887
888        return $ret;
889}
890
891/** Convenient wrapper around ef_run and ef_tpl
892 *
893 * -# Handle the request
894 *   - ef_run() is called which executes whole application logic and returns pure data
895 * -# Draw it
896 *   - ef_tpl() is called which pours the data into templates and returns HTML
897 *
898 * @param $path   efpath
899 * @param $req    request parameters
900 * @param $css    passed to ef_tpl()
901 * @see ef_run()
902 * @see ef_tpl()
903 */
904function ef_req($path, $req = array(), $css = 1)
905{
906        return ef_tpl(ef_run($path, $req), $css);
907}
908
909/** A Django-like URL -> ef translator
910 *
911 * Purpose of this function is to run series of regular expressions against browser-supplied URL address and to
912 * translate it into an ef request on a match. In order to make it convenient arguments to ef request may also be
913 * specified - statically and/or derived from users URL.
914 *
915 * @param $path     path to match against
916 * @param $reqmap   an array of $regexp => $reqdescr entries:
917 *                  - if $reqdescr is a string, PHP function with such name is called (see #1)
918 *                  - otherwise, it must be an array:
919 *                    - $reqdescr[0] has /efpath to request
920 *                      - if path starts with a colon (:) PCRE back references may be used, e.g. if $regexp is
921 *                        /index/(.*) and you pass "/myapp/\1", then for URL of /index/cart/show an efpath of
922 *                        /myapp/cart/show will be requested
923 *                    - $reqdescr[1] holds array of URL -> $req translations, e.g.:
924 *                       - if $regexp is /index/([0-9]+)/([a-z]+) and you pass array("id", "type"), then for URL of
925 *                         /index/179/foobar a $req array("id" => "179", "type" => "foobar") will be generated
926 *                    - $reqdescr[2] has additional $req parts; remember that URL-derived $req is more important
927 *                    - $reqdescr[3] decides if JSON output is requested (true), otherwise (false) HTML is returned
928 * @param $css      to pass to ef_req()
929 * @param $funcname function to call to handle the result of this URL->ef mapping
930 */
931function ef_reqmap($path, $reqmap, $css = false, $funcname = "ef_req")
932{
933        $args;             /* holds matches of preg_match() */
934        $addreq = array(); /* vars from $path to add to $req */
935        $req = array();    /* efreq vars */
936
937        foreach ($reqmap as $regexp => $reqdescr) {
938                $regexp = addcslashes($regexp, '/');
939
940                ef_log(14, "ef_reqmap: checking '/$regexp/'");
941                if (!preg_match("/$regexp/", $path, $args)) continue;
942                ef_log(10, "ef_reqmap: '$regexp' matched: " . print_r($args, 1));
943
944                if (is_string($reqdescr))
945                        return call_user_func_array($reqdescr, $args); /* #1 */
946
947                /* XXX: promote good practices by not allowing to back-reference the whole regexp */
948                array_shift($args);
949
950                /* extracts $req elements from URL */
951                if (is_array($reqdescr[1])) {
952                        foreach ($args as $key => $arg)
953                                if (isset($reqdescr[1][$key]))
954                                        $addreq[$reqdescr[1][$key]] = $arg;
955                }
956
957                if (is_array($reqdescr[2]))
958                        $req = array_merge($reqdescr[2], $addreq);
959                else
960                        $req = &$addreq;
961
962                /* allow to map requested url onto ef action */
963                if ($reqdescr[0][0] == ':') {
964                        $reqdescr[0] = preg_replace("/$regexp/", substr($reqdescr[0], 1), $path);
965                }
966
967                if ($reqdescr[3]) { /* JSON */
968                        $state = ef_run($reqdescr[0], $req);
969                        return json_encode($state["vars"]["out"]);
970                }
971                else {
972                        return $funcname($reqdescr[0], $req, $css);
973                }
974        }
975
976        /* nothing matched */
977        ef_log(1, "invalid action");
978
979        if (!$GLOBALS["ef_cli_mode"] && !headers_sent())
980                header("HTTP/1.0 400 Bad Request");
981
982        return "ef: bad request: $path";
983}
984
985/** Inits ef
986 * Have to be used before any other f-n from this file.
987 */
988function ef_init()
989{
990        global $ef_lang;
991        global $ef_default_lang;
992
993        /* set timezone to UTC before first call to ef_log */
994        date_default_timezone_set("UTC");
995
996        ef_log(3, "ef_init()");
997        set_include_path(get_include_path() . ":$GLOBALS[ef_dir]:$GLOBALS[ef_ctl]");
998
999        ef_log(9, "reading hooks");
1000        ef_read_hooks($GLOBALS["ef_dir"] . EF_HOOKS);
1001
1002        if (isset($_GET["ef_lang"]) && preg_match(REGEXP_LANG, $_GET["ef_lang"])) {
1003                ef_save_lang($_GET["ef_lang"]);
1004        }
1005        else if (!isset($_REQUEST["ef_lang"]) && !$ef_lang) {
1006                /* client connected for the first time (probably), no language saved in cookie */
1007                $lang = substr($_SERVER["HTTP_ACCEPT_LANGUAGE"], 0, 2);
1008
1009                if (preg_match(REGEXP_LANG, $lang)) /* try to extract it from HTTP_ACCEPT_LANGUAGE... */
1010                        ef_save_lang($lang);
1011                else /* ...or choose $ef_default_lang for him */
1012                        ef_save_lang($GLOBALS["ef_default_lang"]);
1013        }
1014        else if (!$ef_lang) {
1015                $ef_lang = $_REQUEST["ef_lang"]; /* usually reads lang from cookie */
1016        }
1017
1018        if (!preg_match(REGEXP_LANG, $ef_lang))
1019                $ef_lang = $ef_default_lang;
1020}
1021
1022// vim: tw=100
Note: See TracBrowser for help on using the repository browser.