CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 1.2 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 1.2
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Classes

  • AclBase
  • AclBehavior
  • AclComponent
  • AclNode
  • AclShell
  • Aco
  • AcoAction
  • AjaxHelper
  • ApcEngine
  • ApiShell
  • App
  • AppController
  • AppHelper
  • AppModel
  • Aro
  • AuthComponent
  • BakeShell
  • BehaviorCollection
  • Cache
  • CacheEngine
  • CacheHelper
  • CakeErrorController
  • CakeLog
  • CakeSchema
  • CakeSession
  • CakeSocket
  • ClassRegistry
  • Component
  • Configure
  • ConnectionManager
  • ConsoleShell
  • ContainableBehavior
  • Controller
  • ControllerTask
  • CookieComponent
  • DataSource
  • DbAcl
  • DbAclSchema
  • DbConfigTask
  • DboAdodb
  • DboDb2
  • DboFirebird
  • DboMssql
  • DboMysql
  • DboMysqlBase
  • DboMysqli
  • DboOdbc
  • DboOracle
  • DboPostgres
  • DboSource
  • DboSqlite
  • DboSybase
  • Debugger
  • EmailComponent
  • ErrorHandler
  • ExtractTask
  • File
  • FileEngine
  • Flay
  • Folder
  • FormHelper
  • Helper
  • HtmlHelper
  • HttpSocket
  • I18n
  • I18nModel
  • i18nSchema
  • I18nShell
  • Inflector
  • IniAcl
  • JavascriptHelper
  • JsHelper
  • JsHelperObject
  • L10n
  • MagicDb
  • MagicFileResource
  • MediaView
  • MemcacheEngine
  • Model
  • ModelBehavior
  • ModelTask
  • Multibyte
  • NumberHelper
  • Object
  • Overloadable
  • Overloadable2
  • PagesController
  • PaginatorHelper
  • Permission
  • PluginTask
  • ProjectTask
  • RequestHandlerComponent
  • Router
  • RssHelper
  • Sanitize
  • Scaffold
  • ScaffoldView
  • SchemaShell
  • Security
  • SecurityComponent
  • SessionComponent
  • SessionHelper
  • SessionsSchema
  • Set
  • Shell
  • String
  • TestSuiteShell
  • TestTask
  • TextHelper
  • ThemeView
  • TimeHelper
  • TranslateBehavior
  • TreeBehavior
  • Validation
  • View
  • ViewTask
  • XcacheEngine
  • Xml
  • XmlElement
  • XmlHelper
  • XmlManager
  • XmlNode
  • XmlTextNode

Functions

  • __enclose
  • make_clean_css
  • mb_encode_mimeheader
  • mb_stripos
  • mb_stristr
  • mb_strlen
  • mb_strpos
  • mb_strrchr
  • mb_strrichr
  • mb_strripos
  • mb_strrpos
  • mb_strstr
  • mb_strtolower
  • mb_strtoupper
  • mb_substr
  • mb_substr_count
  • write_css_cache
  1: <?php
  2: /* SVN FILE: $Id$ */
  3: /**
  4:  * Short description for file.
  5:  *
  6:  * Long description for file
  7:  *
  8:  * PHP versions 4 and 5
  9:  *
 10:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 11:  * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
 12:  *
 13:  * Licensed under The MIT License
 14:  * Redistributions of files must retain the above copyright notice.
 15:  *
 16:  * @copyright     Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
 17:  * @link          http://cakephp.org CakePHP(tm) Project
 18:  * @package       cake
 19:  * @subpackage    cake.cake.libs
 20:  * @since         CakePHP(tm) v 1.2.0.4116
 21:  * @version       $Revision$
 22:  * @modifiedby    $LastChangedBy$
 23:  * @lastmodified  $Date$
 24:  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 25:  */
 26: /**
 27:  * Included libraries.
 28:  */
 29: App::import('Core', 'l10n');
 30: /**
 31:  * Short description for file.
 32:  *
 33:  * Long description for file
 34:  *
 35:  * @package       cake
 36:  * @subpackage    cake.cake.libs
 37:  */
 38: class I18n extends Object {
 39: /**
 40:  * Instance of the I10n class for localization
 41:  *
 42:  * @var I10n
 43:  * @access public
 44:  */
 45:     var $l10n = null;
 46: /**
 47:  * Current domain of translation
 48:  *
 49:  * @var string
 50:  * @access public
 51:  */
 52:     var $domain = null;
 53: /**
 54:  * Current category of translation
 55:  *
 56:  * @var string
 57:  * @access public
 58:  */
 59:     var $category = 'LC_MESSAGES';
 60: /**
 61:  * Current language used for translations
 62:  *
 63:  * @var string
 64:  * @access private
 65:  */
 66:     var $__lang = null;
 67: /**
 68:  * Translation strings for a specific domain read from the .mo or .po files
 69:  *
 70:  * @var array
 71:  * @access private
 72:  */
 73:     var $__domains = array();
 74: /**
 75:  * Set to true when I18N::__bindTextDomain() is called for the first time.
 76:  * If a translation file is found it is set to false again
 77:  *
 78:  * @var boolean
 79:  * @access private
 80:  */
 81:     var $__noLocale = false;
 82: /**
 83:  * Determine what should be cached
 84:  *
 85:  * @var boolean
 86:  * @access private
 87:  */
 88:     var $__cache = array();
 89: /**
 90:  * Set to true when I18N::__bindTextDomain() is called for the first time.
 91:  * If a translation file is found it is set to false again
 92:  *
 93:  * @var array
 94:  * @access private
 95:  */
 96:     var $__categories = array(
 97:          'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME', 'LC_MESSAGES'
 98:     );
 99: /**
100:  * Return a static instance of the I18n class
101:  *
102:  * @return object I18n
103:  * @access public
104:  */
105:     function &getInstance() {
106:         static $instance = array();
107:         if (!$instance) {
108:             $instance[0] =& new I18n();
109:             $instance[0]->l10n =& new L10n();
110:         }
111:         return $instance[0];
112:     }
113: /**
114:  * Used by the translation functions in basics.php
115:  * Can also be used like I18n::translate(); but only if the App::import('I18n'); has been used to load the class.
116:  *
117:  * @param string $singular String to translate
118:  * @param string $plural Plural string (if any)
119:  * @param string $domain Domain
120:  * @param string $category Category
121:  * @param integer $count Count
122:  * @return string translated strings.
123:  * @access public
124:  */
125:     function translate($singular, $plural = null, $domain = null, $category = 6, $count = null) {
126:         $_this =& I18n::getInstance();
127:         
128:         if (strpos($singular, "\r\n") !== false) {
129:             $singular = str_replace("\r\n", "\n", $singular);
130:         }
131:         if ($plural !== null && strpos($plural, "\r\n") !== false) {
132:             $plural = str_replace("\r\n", "\n", $plural);
133:         }
134: 
135:         if (is_numeric($category)) {
136:             $_this->category = $_this->__categories[$category];
137:         }
138:         $language = Configure::read('Config.language');
139: 
140:         if (!empty($_SESSION['Config']['language'])) {
141:             $language = $_SESSION['Config']['language'];
142:         }
143: 
144:         if (($_this->__lang && $_this->__lang !== $language) || !$_this->__lang) {
145:             $lang = $_this->l10n->get($language);
146:             $_this->__lang = $lang;
147:         }
148: 
149:         if (is_null($domain)) {
150:             $domain = 'default';
151:         }
152:         $_this->domain = $domain . '_' . $_this->l10n->locale;
153: 
154:         if (!isset($_this->__domains[$_this->category][$_this->__lang][$domain])) {
155:             $_this->__domains[$_this->category][$_this->__lang][$domain] = Cache::read($_this->domain, '_cake_core_');
156:         }
157: 
158:         if (empty($_this->__domains[$_this->category][$_this->__lang][$domain])) {
159:             $_this->__bindTextDomain($domain);
160:             $_this->__cache[] = array(
161:                 'key' => $_this->domain,
162:                 'category' => $_this->category,
163:                 'lang' => $_this->__lang,
164:                 'domain' => $domain,
165:                 'locale' => $_this->l10n->locale
166:             );
167:         }
168: 
169:         if (!isset($count)) {
170:             $plurals = 0;
171:         } elseif (!empty($_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"]) && $_this->__noLocale === false) {
172:             $header = $_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"];
173:             $plurals = $_this->__pluralGuess($header, $count);
174:         } else {
175:             if ($count != 1) {
176:                 $plurals = 1;
177:             } else {
178:                 $plurals = 0;
179:             }
180:         }
181: 
182:         if (!empty($_this->__domains[$_this->category][$_this->__lang][$domain][$singular])) {
183:             if (($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$singular]) || ($plurals) && ($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$plural])) {
184:                 if (is_array($trans)) {
185:                     if (isset($trans[$plurals])) {
186:                         $trans = $trans[$plurals];
187:                     }
188:                 }
189:                 if (strlen($trans)) {
190:                     $singular = $trans;
191:                     return $singular;
192:                 }
193:             }
194:         }
195: 
196:         if (!empty($plurals)) {
197:             return($plural);
198:         }
199:         return($singular);
200:     }
201: /**
202:  * Attempts to find the plural form of a string.
203:  *
204:  * @param string $header Type
205:  * @param integrer $n Number
206:  * @return integer plural match
207:  * @access private
208:  */
209:     function __pluralGuess($header, $n) {
210:         if (!is_string($header) || $header === "nplurals=1;plural=0;" || !isset($header[0])) {
211:             return 0;
212:         }
213: 
214:         if ($header === "nplurals=2;plural=n!=1;") {
215:             return $n != 1 ? 1 : 0;
216:         } elseif ($header === "nplurals=2;plural=n>1;") {
217:             return $n > 1 ? 1 : 0;
218:         }
219: 
220:         if (strpos($header, "plurals=3")) {
221:             if (strpos($header, "100!=11")) {
222:                 if (strpos($header, "10<=4")) {
223:                     return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
224:                 } elseif (strpos($header, "100<10")) {
225:                     return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
226:                 }
227:                 return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n != 0 ? 1 : 2);
228:             } elseif (strpos($header, "n==2")) {
229:                 return $n == 1 ? 0 : ($n == 2 ? 1 : 2);
230:             } elseif (strpos($header, "n==0")) {
231:                 return $n == 1 ? 0 : ($n == 0 || ($n % 100 > 0 && $n % 100 < 20) ? 1 : 2);
232:             } elseif (strpos($header, "n>=2")) {
233:                 return $n == 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2);
234:             } elseif (strpos($header, "10>=2")) {
235:                 return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
236:             }
237:             return $n % 10 == 1 ? 0 : ($n % 10 == 2 ? 1 : 2);
238:         } elseif (strpos($header, "plurals=4")) {
239:             if (strpos($header, "100==2")) {
240:                 return $n % 100 == 1 ? 0 : ($n % 100 == 2 ? 1 : ($n % 100 == 3 || $n % 100 == 4 ? 2 : 3));
241:             } elseif (strpos($header, "n>=3")) {
242:                 return $n == 1 ? 0 : ($n == 2 ? 1 : ($n == 0 || ($n >= 3 && $n <= 10) ? 2 : 3));
243:             } elseif (strpos($header, "100>=1")) {
244:                 return $n == 1 ? 0 : ($n == 0 || ($n % 100 >= 1 && $n % 100 <= 10) ? 1 : ($n % 100 >= 11 && $n % 100 <= 20 ? 2 : 3));
245:             }
246:         } elseif (strpos($header, "plurals=5")) {
247:             return $n == 1 ? 0 : ($n == 2 ? 1 : ($n >= 3 && $n <= 6 ? 2 : ($n >= 7 && $n <= 10 ? 3 : 4)));
248:         }
249:     }
250: /**
251:  * Binds the given domain to a file in the specified directory.
252:  *
253:  * @param string $domain Domain to bind
254:  * @return string Domain binded
255:  * @access private
256:  */
257:     function __bindTextDomain($domain) {
258:         $this->__noLocale = true;
259:         $core = true;
260:         $merge = array();
261:         $searchPaths = Configure::read('localePaths');
262:         $plugins = Configure::listObjects('plugin');
263: 
264:         if (!empty($plugins)) {
265:             $pluginPaths = Configure::read('pluginPaths');
266: 
267:             foreach ($plugins as $plugin) {
268:                 $plugin = Inflector::underscore($plugin);
269:                 if ($plugin === $domain) {
270:                     foreach ($pluginPaths as $pluginPath) {
271:                         $searchPaths[] = $pluginPath . $plugin . DS . 'locale' . DS;
272:                     }
273:                     $searchPaths = array_reverse($searchPaths);
274:                     break;
275:                 }
276:             }
277:         }
278: 
279: 
280:         foreach ($searchPaths as $directory) {
281: 
282:             foreach ($this->l10n->languagePath as $lang) {
283:                 $file = $directory . $lang . DS . $this->category . DS . $domain;
284: 
285:                 if ($core) {
286:                     $app = $directory . $lang . DS . $this->category . DS . 'core';
287: 
288:                     if (file_exists($fn = "$app.mo")) {
289:                         $this->__loadMo($fn, $domain);
290:                         $this->__noLocale = false;
291:                         $merge[$this->category][$this->__lang][$domain] = $this->__domains[$this->category][$this->__lang][$domain];
292:                         $core = null;
293:                     } elseif (file_exists($fn = "$app.po") && ($f = fopen($fn, "r"))) {
294:                         $this->__loadPo($f, $domain);
295:                         $this->__noLocale = false;
296:                         $merge[$this->category][$this->__lang][$domain] = $this->__domains[$this->category][$this->__lang][$domain];
297:                         $core = null;
298:                     }
299:                 }
300: 
301:                 if (file_exists($fn = "$file.mo")) {
302:                     $this->__loadMo($fn, $domain);
303:                     $this->__noLocale = false;
304:                     break 2;
305:                 } elseif (file_exists($fn = "$file.po") && ($f = fopen($fn, "r"))) {
306:                     $this->__loadPo($f, $domain);
307:                     $this->__noLocale = false;
308:                     break 2;
309:                 }
310:             }
311:         }
312: 
313:         if (empty($this->__domains[$this->category][$this->__lang][$domain])) {
314:             $this->__domains[$this->category][$this->__lang][$domain] = array();
315:             return($domain);
316:         }
317: 
318:         if ($head = $this->__domains[$this->category][$this->__lang][$domain][""]) {
319:             foreach (explode("\n", $head) as $line) {
320:                 $header = strtok($line,":");
321:                 $line = trim(strtok("\n"));
322:                 $this->__domains[$this->category][$this->__lang][$domain]["%po-header"][strtolower($header)] = $line;
323:             }
324: 
325:             if (isset($this->__domains[$this->category][$this->__lang][$domain]["%po-header"]["plural-forms"])) {
326:                 $switch = preg_replace("/(?:[() {}\\[\\]^\\s*\\]]+)/", "", $this->__domains[$this->category][$this->__lang][$domain]["%po-header"]["plural-forms"]);
327:                 $this->__domains[$this->category][$this->__lang][$domain]["%plural-c"] = $switch;
328:                 unset($this->__domains[$this->category][$this->__lang][$domain]["%po-header"]);
329:             }
330:             $this->__domains = Set::pushDiff($this->__domains, $merge);
331: 
332:             if (isset($this->__domains[$this->category][$this->__lang][$domain][null])) {
333:                 unset($this->__domains[$this->category][$this->__lang][$domain][null]);
334:             }
335:         }
336:         return($domain);
337:     }
338: /**
339:  * Loads the binary .mo file for translation and sets the values for this translation in the var I18n::__domains
340:  *
341:  * @param resource $file Binary .mo file to load
342:  * @param string $domain Domain where to load file in
343:  * @access private
344:  */
345:     function __loadMo($file, $domain) {
346:         $data = file_get_contents($file);
347: 
348:         if ($data) {
349:             $header = substr($data, 0, 20);
350:             $header = unpack("L1magic/L1version/L1count/L1o_msg/L1o_trn", $header);
351:             extract($header);
352: 
353:             if ((dechex($magic) == '950412de' || dechex($magic) == 'ffffffff950412de') && $version == 0) {
354:                 for ($n = 0; $n < $count; $n++) {
355:                     $r = unpack("L1len/L1offs", substr($data, $o_msg + $n * 8, 8));
356:                     $msgid = substr($data, $r["offs"], $r["len"]);
357:                     unset($msgid_plural);
358: 
359:                     if (strpos($msgid, "\000")) {
360:                         list($msgid, $msgid_plural) = explode("\000", $msgid);
361:                     }
362:                     $r = unpack("L1len/L1offs", substr($data, $o_trn + $n * 8, 8));
363:                     $msgstr = substr($data, $r["offs"], $r["len"]);
364: 
365:                     if (strpos($msgstr, "\000")) {
366:                         $msgstr = explode("\000", $msgstr);
367:                     }
368:                     $this->__domains[$this->category][$this->__lang][$domain][$msgid] = $msgstr;
369: 
370:                     if (isset($msgid_plural)) {
371:                         $this->__domains[$this->category][$this->__lang][$domain][$msgid_plural] =& $this->__domains[$this->category][$this->__lang][$domain][$msgid];
372:                     }
373:                 }
374:             }
375:         }
376:     }
377: /**
378:  * Loads the text .po file for translation and sets the values for this translation in the var I18n::__domains
379:  *
380:  * @param resource $file Text .po file to load
381:  * @param string $domain Domain to load file in
382:  * @return array Binded domain elements
383:  * @access private
384:  */
385:     function __loadPo($file, $domain) {
386:         $type = 0;
387:         $translations = array();
388:         $translationKey = "";
389:         $plural = 0;
390:         $header = "";
391: 
392:         do {
393:             $line = trim(fgets($file, 1024));
394:             if ($line == "" || $line[0] == "#") {
395:                 continue;
396:             }
397:             if (preg_match("/msgid[[:space:]]+\"(.+)\"$/i", $line, $regs)) {
398:                 $type = 1;
399:                 $translationKey = stripcslashes($regs[1]);
400:             } elseif (preg_match("/msgid[[:space:]]+\"\"$/i", $line, $regs)) {
401:                 $type = 2;
402:                 $translationKey = "";
403:             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && ($type == 1 || $type == 2 || $type == 3)) {
404:                 $type = 3;
405:                 $translationKey .= stripcslashes($regs[1]);
406:             } elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
407:                 $translations[$translationKey] = stripcslashes($regs[1]);
408:                 $type = 4;
409:             } elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
410:                 $type = 4;
411:                 $translations[$translationKey] = "";
412:             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 4 && $translationKey) {
413:                 $translations[$translationKey] .= stripcslashes($regs[1]);
414:             } elseif (preg_match("/msgid_plural[[:space:]]+\".*\"$/i", $line, $regs)) {
415:                 $type = 6;
416:             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 6 && $translationKey) {
417:                 $type = 6;
418:             } elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) {
419:                 $plural = $regs[1];
420:                 $translations[$translationKey][$plural] = stripcslashes($regs[2]);
421:                 $type = 7;
422:             } elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) {
423:                 $plural = $regs[1];
424:                 $translations[$translationKey][$plural] = "";
425:                 $type = 7;
426:             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 7 && $translationKey) {
427:                 $translations[$translationKey][$plural] .= stripcslashes($regs[1]);
428:             } elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && $type == 2 && !$translationKey) {
429:                 $header .= stripcslashes($regs[1]);
430:                 $type = 5;
431:             } elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && !$translationKey) {
432:                 $header = "";
433:                 $type = 5;
434:             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 5) {
435:                 $header .= stripcslashes($regs[1]);
436:             } else {
437:                 unset($translations[$translationKey]);
438:                 $type = 0;
439:                 $translationKey = "";
440:                 $plural = 0;
441:             }
442:         } while (!feof($file));
443:         fclose($file);
444:         $merge[""] = $header;
445:         return $this->__domains[$this->category][$this->__lang][$domain] = array_merge($merge ,$translations);
446:     }
447: /**
448:  * Object destructor
449:  *
450:  * Write cache file if changes have been made to the $__map or $__paths
451:  * @access private
452:  */
453:     function __destruct() {
454:         if (!empty($this->__cache)) {
455:             foreach($this->__cache as $entry) {
456:                 if (empty($this->__domains[$entry['category']][$entry['lang']][$entry['domain']])) {
457:                     continue;
458:                 }
459:                 Cache::write(
460:                     $entry['key'],
461:                     array_filter($this->__domains[$entry['category']][$entry['lang']][$entry['domain']]),
462:                     '_cake_core_'
463:                 );
464:             }
465:         }
466:         $this->__cache = array();
467:         $this->__domains = array();
468:     }
469: }
470: ?>
471: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs