diff --git a/README-GIT.md b/README-GIT.md index 9a99309eea..874b2a6fdc 100644 --- a/README-GIT.md +++ b/README-GIT.md @@ -1,17 +1,23 @@ -# USING THE GIT REPOSITORY TO WORK ON PI ENGINE - 1. Setup a GitHub account (http://github.com/), if you haven't yet - 2. Clone the repo locally and enter it (use your own GitHub username - in the statement below) +Pi Engine Github Skeleton +========================= - ```sh - % git clone https://github.com/pi-engine/pi.git - % cd pi - ``` +Pi Engine Core +---------------- +* [pi-engine/pi](https://github.com/pi-engine/pi): Pi Engine core repo +* [pi-engine/pi/wiki](https://github.com/pi-engine/pi/wiki): Pi Engine documents -# GIT REPO SKELETON FOR PI ENGINE -* [pi-engine/pi](https://github.com/pi-engine/pi): Pi Engine core repo -* pi-engine/pi-{modulename}: repo for module {modulename}, for instance [pi-engine/pi-tag](https://github.com/pi-engine/pi-tag) for module tag -* pi-engine/pi-theme-{themename}: repo for theme {themename}, for instance [pi-engine/pi-theme-pi](https://github.com/pi-engine/pi-theme-pi) for theme pi -* pi-engine/{reponame}: repo for non-pi components +Pi Engine Module +---------------- +* [pi-module](https://github.com/pi-module): repos for modules +* Eeach module has its own repo, for instance [pi-module/tag](https://github.com/pi-module/tag) for module tag + +Pi Engine Theme +--------------- +* [pi-theme](https://github.com/pi-theme): repos for themes +* Each theme has its ownrepo, for instance [pi-theme/pi](https://github.com/pi-theme/pi) for theme pi + +Pi Engine Extras +---------------- +* [pi-extra](https://github.com/pi-extra): repos for extra components diff --git a/README.md b/README.md index b27e7af501..c0752b0010 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,37 @@ Pi Engine ================= Pi Engine is a role oriented application development engine for web and mobile, designed as the next generation and a successor to Xoops. -Pi is developed upon PHP and MySQL with selected third-party frameworks including but not limited to [Zend Framework library](https://github.com/zendframework/zf2), [jQuery](https://github.com/jquery/jquery), [Bootstrap](https://github.com/twitter/bootstrap) and [Backbone](https://github.com/documentcloud/backbone). +Pi is developed upon PHP and MySQL with selected third-party frameworks including but not limited to [Zend Framework 2](https://github.com/zendframework/zf2), jQuery, Bootstrap and Backbone. -Pi Project follows the philosophy of open standard, open design, open development and open structure. Pi is born as a complete open source project and intended to build a sustainable ecosystem that benefits all contributors and users. +Pi Project follows the philosophy of open standard, open design, open development and open management. Pi is born as a complete open source project and intended to build a sustainable ecosystem that benefits all contributors and users. Pi Engine is started by [Taiwen Jiang](http://github.com/taiwen) (Xoops Project Lead and Dev Lead), [Marc Desrousseaux](http://github.com/MarcoXoops) (Xoops QA Lead), [Hossein Azizabadi](http://github.com/voltan) (Xoops Persian Lead), [Kris](http://github.com/krisxoofoo) (Xoops Council Member and Xoops France Lead), etc. [Pi Team](https://github.com/pi-engine/pi/wiki/Pi-Team) is highly inspired by the Xoops led by onokazu (Ono Kazumi), skalpa and phppp (Taiwen Jiang, or D.J.) successively since 2001 and will continue to support and promote Xoops. **Check out [Latest Release](https://github.com/pi-engine/pi/blob/master/doc/releasenotes.md).** +Highlights +------------- +1. **Sustainable ecosystem:** A sustainable ecosystem built upon open standard, open source code, open development and open management on Github. +2. **Engineered development:** Quality ensured engineering development with short learning curve, low skill requirements with clean MVC architecture, semantic templating, sophisticated API and strict starndards. +3. **Visualized management:** Easy and responsive application and content management based on visualized mangement tools and interface with page and widget mechanism. +4. **Agile workflow:** Role oriented architecture and deployment skeleton supports manageable agile development workflow. Features and practices ---------------------- -1. Modularization for functionality and applications -2. Components for basic libraries and services for fundamental system functions -3. Theming for presentation and appearance -4. Design-friendly templating -5. DevOps oriented architecture -6. Centralized security enhancement +* Modularization for functionality and applications +* Components for basic libraries and services for fundamental system functions +* Theming for presentation and appearance +* Design-friendly templating +* DevOps oriented architecture +* Centralized security enhancement Quick start ----------- -* Documents at [Pi Wiki](https://github.com/pi-engine/pi/wiki) -* Download the [latest stable code](https://github.com/xoops/pi/zipball/master) and [latest dev code](https://github.com/xoops/pi/zipball/develop). -* Clone Pi repo `git clone git://github.com/pi-engine/pi.git` and [keep updated](https://help.github.com/articles/fork-a-repo#pull-in-upstream-changes). -* Demo sites with shared deployment: Pi Dialogue ([pialog.org](http://pialog.org) | [pialogue.org](http://pialogue.org)), Pi Demo ([pi-demo.org](http://pi-demo.org)) and Xoops/Pi ([xoopsengine.org](http://demo.xoopsengine.org)). +* Documents at [Pi Wiki](https://github.com/pi-engine/pi/wiki). +* Download the [latest stable code](https://github.com/pi-engine/pi/zipball/master) and [latest dev code](https://github.com/pi-engine/pi/zipball/develop). +* Clone Pi repo `git clone git://github.com/pi-engine/pi.git`. +* Get extension resources at [Pi module and theme repos](https://github.com/pi-engine/pi/blob/master/README-GIT.md). Development ---------- @@ -40,3 +46,9 @@ Copyright and License The Engine is released under a [New BSD License](https://github.com/pi-engine/pi/blob/master/doc/license.txt). +Demos +----- +Demo sites with shared deployment: +* Pi Dialogue ([pialog.org](http://pialog.org) | [pialogue.org](http://pialogue.org)) +* Pi Demo ([pi-demo.org](http://pi-demo.org)) +* Xoops/Pi ([xoopsengine.org](http://demo.xoopsengine.org)) diff --git a/doc/PiArchitectureDiagram-TaiwenJiang.pdf b/doc/PiArchitectureDiagram-TaiwenJiang.pdf new file mode 100644 index 0000000000..17e063394e Binary files /dev/null and b/doc/PiArchitectureDiagram-TaiwenJiang.pdf differ diff --git a/doc/README.html b/doc/README.html new file mode 100644 index 0000000000..f9a5410044 --- /dev/null +++ b/doc/README.html @@ -0,0 +1,34 @@ +
+ Pi Engine is a role oriented application development engine for web and mobile, designed as the next generation and a successor to Xoops. + Pi is developed upon PHP and MySQL with selected third-party frameworks including but not limited to Zend Framework library, jQuery, Bootstrap and Backbone. +
+ ++ Pi Project follows the philosophy of open standard, open design, open development and open structure. Pi is born as a complete open source project and intended to build a sustainable ecosystem that benefits all contributors and users. +
+ ++ The Engine is released under a New BSD License. +
+ ++
+
+ * _t('Message to be translated and used later.');
+ *
+ * // Use case, register module config
+ * // Registered in a module's config.php
+ *
+ * $config['key'] = array('title' => _t('Config Title'), 'description' => _t('Config hint'), '...'));
+ *
+ * // Load translated message in the module config setting page
+ * // module/system/src/Controller/Admin/ConfigController.php calls ConfigForm.php:
+ *
+ * // Module\System\Form\ConfigForm::addElement()
+ *
+ * protected function addElement($config)
+ * {
+ * // ...
+ * $attributes['description'] = __($config->description);
+ * $options = array(
+ * 'label' => __($config->title),
+ * 'module' => $this->module,
+ * );
+ * // ...
+ * }
+ *
+ *
+ *
+ * 8. Format a date
*
* _date(time(), 'fa-IR', 'long', 'short', 'Asia/Tehran', 'persian');
* _date(time(), array('locale' => 'fa-IR', 'datetype' => 'long', 'timetype' => 'short', 'timezone' => 'Asia/Tehran', 'calendar' => 'persian'));
@@ -167,7 +193,7 @@
* _date(time(), ...);
*
*
- * 8. Format a number
+ * 9. Format a number
*
* _number(123.4567, 'decimal', '#0.# kg', 'zh-CN', 'default');
* _number(123.4567, 'decimal', '#0.# kg', 'zh-CN');
@@ -175,14 +201,14 @@
* _number(123.4567, 'spellout');
*
*
- * 9. Format a currency
+ * 10. Format a currency
*
* _currency(123.45, 'USD', 'en-US');
* _currency(123.45, 'USD');
* _currency(123.45);
*
*
- * 10. Get a date formatter
+ * 11. Get a date formatter
*
* Pi::service('i18n')->getDateFormatter('fa-IR', 'long', 'short', 'Asia/Tehran', 'persian');
* Pi::service('i18n')->getDateFormatter(array('locale' => 'fa-IR', 'datetype' => 'long', 'timetype' => 'short', 'timezone' => 'Asia/Tehran', 'calendar' => 'persian'));
@@ -194,7 +220,7 @@
* Pi::service('i18n')->getDateFormatter(array('pattern' => 'yyyy-MM-dd HH:mm:ss'));
*
*
- * 11. Get a number formatter
+ * 12. Get a number formatter
*
* // Get a number formatter
* Pi::service('i18n')->getNumberFormatter('decimal', '#0.# kg', 'zh-CN');
@@ -392,7 +418,7 @@ public function __get($name)
* Normalize domain in Intl resources, including Translator, Locale, Date, NumberFormatter, etc.
*
* @param string $domain
- * @return array pair of component and domain
+ * @return array pair of component and domain
*/
public function normalizeDomain($rawDomain)
{
@@ -410,17 +436,19 @@ public function normalizeDomain($rawDomain)
*
* @param array|string $domain
* @param string|null $locale
- * @return Intl
+ * @return I18n
*/
public function load($domain, $locale = null)
{
$domain = is_array($domain) ? $domain : $this->normalizeDomain($domain);
$locale = $locale ?: $this->getLocale();
-
- $this->getTranslator()->load($domain, $locale);
+ $result = $this->getTranslator()->load($domain, $locale);
if (Pi::service()->hasService('log')) {
- Pi::service()->getService('log')->info(sprintf('Translation "%s" is loaded', implode(':', $domain)));
+ $message = $result
+ ? sprintf('Translation "%s.%s" is loaded.', implode(':', $domain), $locale)
+ : sprintf('Translation "%s.%s" is empty.', implode(':', $domain), $locale);
+ Pi::service()->getService('log')->info($message);
}
return $this;
@@ -432,14 +460,14 @@ public function load($domain, $locale = null)
* @param string $domain
* @param string $module
* @param string $locale
- * @return Intl
+ * @return I18n
*/
public function loadModule($domain, $module = null, $locale = null)
{
$module = $module ?: Pi::service('module')->current();
$component = array('module/' . $module, $domain);
-
$this->load($component, $locale);
+
return $this;
}
@@ -449,13 +477,14 @@ public function loadModule($domain, $module = null, $locale = null)
* @param string $domain
* @param string $theme
* @param string $locale
- * @return Intl
+ * @return I18n
*/
public function loadTheme($domain, $theme = null, $locale = null)
{
$theme = $theme ?: Pi::service('theme')->current();
$component = array('theme/' . $theme, $domain);
$this->load($component, $locale);
+
return $this;
}
@@ -688,6 +717,18 @@ function _e($message, $domain = null, $locale = null)
echo __($message, $domain, $locale);
}
+ /**
+ * Register a message to translation queue
+ *
+ *
+ * @param string $message The string to be localized
+ * @return void
+ */
+ function _t($message)
+ {
+ return $message;
+ }
+
/**
* Check if Intl functions are available
*/
diff --git a/lib/Pi/Application/Service/Log.php b/lib/Pi/Application/Service/Log.php
index a6636aceb0..ebe879206a 100644
--- a/lib/Pi/Application/Service/Log.php
+++ b/lib/Pi/Application/Service/Log.php
@@ -33,7 +33,7 @@ class Log extends AbstractService
/**
* Whether or not to the service is active
- * @var boolean
+ * @var bool
*/
protected $active;
protected $debugger;
@@ -133,6 +133,21 @@ public function active($flag = null)
return $this->active;
}
+ /**
+ * Enable/disable debugger
+ *
+ * @param bool $flag
+ * @return bool|null return previous muted value or null if no debugger available
+ */
+ public function mute($flag = true)
+ {
+ $muted = null;
+ if ($this->debugger) {
+ $muted = $this->debugger->mute($flag);
+ }
+ return $muted;
+ }
+
/**
* Get logger, instantiate it if not available
*
diff --git a/lib/Pi/File/Transfer/Download.php b/lib/Pi/File/Transfer/Download.php
new file mode 100644
index 0000000000..58cc0cff81
--- /dev/null
+++ b/lib/Pi/File/Transfer/Download.php
@@ -0,0 +1,319 @@
+
+ * @package Pi\File
+ */
+
+namespace Pi\File\Transfer;
+
+use Pi;
+use Pi\Filter\File\Rename;
+use ZipArchive;
+
+/**
+ * Download content and files
+ *
+ * Download conent generated on-fly
+ *
+ * $file = array(
+ * 'source' => 'Generated content',
+ * // Required
+ * 'type' => 'string',
+ * // Optional
+ * 'filename' => 'pi-download',
+ * // Optional
+ * 'content_type => 'application/octet-stream',
+ * );
+ * $downloader = new Download;
+ * $downloader->send($file)
+ *
+ *
+ * Download a file
+ *
+ * $file = 'path/to/file';
+ * // Or
+ * $file = array(
+ * 'source' => 'path/to/file'
+ * // Optional
+ * 'filename' => 'pi-download',
+ * // Optional
+ * 'content_type => 'application/octet-stream',
+ * );
+ * $downloader = new Download;
+ * $downloader->send($file)
+ *
+ *
+ * Download multiple files, compressed and sent as a zip file
+ *
+ * $file = array(
+ * 'path/to/file1',
+ * 'path/to/file2',
+ * 'path/to/file3',
+ * );
+ * // Or
+ * $file = array(
+ * array(
+ * 'filename' => 'path/to/file1',
+ * 'localname' => 'filea',
+ * ),
+ * array(
+ * 'filename' => 'path/to/file2',
+ * 'localname' => 'fileb',
+ * ),
+ * array(
+ * 'filename' => 'path/to/file3',
+ * 'localname' => 'fileb',
+ * ),
+ * );
+ * // Or
+ * $file = array(
+ * 'source' => array(
+ * 'path/to/file1',
+ * 'path/to/file2',
+ * 'path/to/file3',
+ * ),
+ * // Optional
+ * 'filename' => 'pi-download',
+ * // Optional
+ * 'type' => 'zip',
+ * // Optional
+ * 'content_type => 'application/octet-stream',
+ * );
+ * // Or
+ * $file = array(
+ * 'source' => array(
+ * array(
+ * 'filename' => 'path/to/file1',
+ * 'localname' => 'filea',
+ * ),
+ * array(
+ * 'filename' => 'path/to/file2',
+ * 'localname' => 'fileb',
+ * ),
+ * array(
+ * 'filename' => 'path/to/file3',
+ * 'localname' => 'fileb',
+ * ),
+ * ),
+ * // Optional
+ * 'filename' => 'pi-download',
+ * // Optional
+ * 'type' => 'zip',
+ * // Optional
+ * 'content_type => 'application/octet-stream',
+ * );
+ * $downloader = new Download;
+ * $downloader->send($file)
+ *
+ *
+ * Download without auto exit
+ *
+ * $downloader = new Download(array('exit' => false));
+ * $downloader->send(array(...));
+ * // Do something
+ * exit;
+ *
+ */
+class Download
+{
+ /**
+ * Exit current execution after the download
+ *
+ * @var bool
+ */
+ protected $exit = true;
+
+ /**
+ * Path to temporary file for zip file
+ *
+ * @var string
+ */
+ protected $tmp = '';
+
+ /**
+ * Creates a file download handler
+ *
+ * @param array $options OPTIONAL Options
+ */
+ public function __construct($options = array())
+ {
+ $this->setOptions($options);
+ }
+
+ /**
+ * Set options
+ *
+ * @param array $options
+ * @return Download
+ */
+ public function setOptions($options = array())
+ {
+ if (isset($options['exit'])) {
+ $this->exit = (bool) $options['exit'];
+ }
+ $this->tmp = isset($options['tmp']) ? $options['tmp'] : Pi::path('cache');
+ return $this;
+ }
+
+ /**
+ * Send the file to the client (Download)
+ *
+ * @param string|array $options Options for the file(s) to send
+ * @return bool
+ */
+ public function send($options = null)
+ {
+ // Disable logging service
+ Pi::service('log')->mute();
+
+ // Canonize download options
+ $options = $this->canonizeDownload($options);
+ if (!$options) {
+ return false;
+ }
+ list($resource, $filename, $type, $contentType, $contentLength) = $options;
+ if ('string' == $type) {
+ $source = $resource;
+ } else {
+ $source = fopen($resource, 'rb');
+ }
+
+ // Send the content to client
+ $this->download($source, $filename, $contentType, $contentLength);
+
+ // Close resource handler
+ if (is_resource($source)) {
+ fclose($source);
+ }
+
+ // Remove tmp zip file
+ if ('zip' == $type) {
+ @unlink($resource);
+ }
+
+ if ($this->exit) {
+ // Exit request to avoid extra output
+ exit;
+ }
+ return true;
+ }
+
+ /**
+ * Canonize download options
+ *
+ * @param array|string $options
+ * @return array
+ */
+ protected function canonizeDownload($options)
+ {
+ $resource = null;
+ $filename = '';
+ $contentType = 'application/octet-stream';
+ $contentLength = 0;
+
+ $source = array();
+ if (is_array($options) && isset($options['source'])) {
+ $source = (array) $options['source'];
+ $type = isset($options['type']) ? $options['type'] : 'file';
+ $filename = isset($options['filename']) ? $options['filename'] : '';
+ $contentType = isset($options['content_type']) ? $options['content_type'] : '';
+ } else {
+ $source = (array) $options;
+ }
+ if (count($source) > 1) {
+ $type = 'zip';
+ }
+
+ if ('string' == $type) {
+ $resource = array_shift($source);
+ $contentLength = strlen($resource);
+ $source = array();
+ } elseif ('zip' != $type) {
+ $resource = array_shift($source);
+ $filename = $filename ?: basename($resource);
+ $contentLength = filesize($resource);
+ } else {
+ if ($filename) {
+ if (strtolower(substr($filename, -4)) != '.zip') {
+ $filename .= '.zip';
+ }
+ } else {
+ $filename = 'archive.zip';
+ }
+ $contentType = 'application/zip';
+ $zipFile = tempnam($this->tmp, 'zip');
+ $zip = new ZipArchive;
+ if ($zip->open($zipFile, ZipArchive::CREATE) !== true) {
+ return array();
+ }
+
+ foreach ($source as $item) {
+ $localname = null;
+ $file = null;
+ if (is_array($item)) {
+ $file = $item['filename'];
+ $localname = isset($item['localname']) ? $item['localname'] : basename($file);
+ } elseif (is_file($item)) {
+ $file = $item;
+ $localname = basename($file);
+ } else {
+ continue;
+ }
+ $zip->addFile($file, $localname);
+ }
+ $zip->close();
+ $resource = $zipFile;
+ $contentLength = filesize($resource);
+ }
+
+ return array($resource, $filename, $type, $contentType, $contentLength);
+ }
+
+ /**
+ * Send content to client
+ *
+ * @param Resource|string $source
+ * @param string $filename
+ * @param string $contentType
+ * @param int $contentLength
+ * @return bool
+ */
+ protected function download($source, $filename, $contentType, $contentLength = 0)
+ {
+ header('Content-Description: File Transfer');
+ header('Content-Type: ' . $contentType);
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Content-Transfer-Encoding: chunked');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header('Pragma: public');
+ if ($contentLength) {
+ header('Content-Length: ' . $contentLength);
+ }
+
+ ob_clean();
+ flush();
+ if (is_resource($source)) {
+ //Send the content in chunks
+ while (false !== ($chunk = fread($source, 4096))) {
+ echo $chunk;
+ }
+ } elseif (is_string($source)) {
+ echo $source;
+ }
+
+ return true;
+ }
+
+}
diff --git a/lib/Pi/File/Transfer/Upload.php b/lib/Pi/File/Transfer/Upload.php
index 75f512a084..5f44e4694a 100644
--- a/lib/Pi/File/Transfer/Upload.php
+++ b/lib/Pi/File/Transfer/Upload.php
@@ -22,6 +22,11 @@
class Upload extends Transfer
{
+ /**
+ * Destination path for uploaded files
+ *
+ * @var string
+ */
protected $destination;
/**
@@ -29,7 +34,6 @@ class Upload extends Transfer
*
* @param array $options OPTIONAL Options to set for this adapter
* @param string $adapter Adapter to use
- * @throws Exception\InvalidArgumentException
*/
public function __construct($options = array(), $adapter = 'Http')
{
@@ -44,10 +48,7 @@ public function __construct($options = array(), $adapter = 'Http')
/**
* Returns adapter
*
- * @param boolean $direction On null, all directions are returned
- * On false, download direction is returned
- * On true, upload direction is returned
- * @return array|Adapter\AbstractAdapter
+ * {@inheriteDoc}
* @note Zend\File\Transfer\Transfer does not support for $direction = 1 yet
*/
public function getAdapter($direction = false)
@@ -81,6 +82,11 @@ public function setDestination($value, $verify = true)
return $this;
}
+ /**
+ * Get upload destination path
+ *
+ * @return string
+ */
public function getDestination()
{
return $this->destination;
diff --git a/lib/Pi/I18n/Translator/Loader/Csv.php b/lib/Pi/I18n/Translator/Loader/Csv.php
index 59ed2cd647..446f834d1c 100644
--- a/lib/Pi/I18n/Translator/Loader/Csv.php
+++ b/lib/Pi/I18n/Translator/Loader/Csv.php
@@ -22,11 +22,22 @@
use Pi;
use Zend\I18n\Translator\Loader\FileLoaderInterface;
use Zend\I18n\Translator\TextDomain;
+use Zend\I18n\Exception;
+use Zend\Stdlib\ErrorHandler;
class Csv implements FileLoaderInterface
{
+ /**
+ * File extension
+ * @var string
+ */
protected $fileExtension = '.csv';
+ /**
+ * Options for CSV file
+ * @var array
+ * @see http://www.php.net/manual/en/function.fgetcsv.php
+ */
protected $options = array(
'delimiter' => ',',
'length' => 0,
@@ -47,38 +58,37 @@ public function setOptions($options = array())
/**
* {@inheritdoc}
+ * @return TextDomain|false
*/
public function load($locale, $filename)
{
$filename .= $this->fileExtension;
$messages = array();
- if (is_file($filename) && is_readable($filename)) {
- $file = @fopen($filename, 'rb');
- while(($data = fgetcsv($file, $this->options['length'], $this->options['delimiter'], $this->options['enclosure'])) !== false) {
- if (substr($data[0], 0, 1) === '#') {
- continue;
- }
+ ErrorHandler::start();
+ $file = fopen($filename, 'rb');
+ $error = ErrorHandler::stop();
+ if (false === $file) {
+ return false;
+ }
- if (!isset($data[1])) {
- continue;
- }
+ while(($data = fgetcsv($file, $this->options['length'], $this->options['delimiter'], $this->options['enclosure'])) !== false) {
+ if (substr($data[0], 0, 1) === '#') {
+ continue;
+ }
- if (count($data) == 2) {
- $messages[$data[0]] = $data[1];
- } else {
- $singular = array_shift($data);
- $messages[$singular] = $data;
- }
+ if (!isset($data[1])) {
+ continue;
}
- } else {
- if (Pi::service()->hasService('log')) {
- Pi::service()->getService('log')->info(sprintf('Translation file is not loaded: %s', $filename));
+
+ if (count($data) == 2) {
+ $messages[$data[0]] = $data[1];
+ } else {
+ $singular = array_shift($data);
+ $messages[$singular] = $data;
}
}
- //return $messages;
-
$textDomain = new TextDomain($messages);
return $textDomain;
}
diff --git a/lib/Pi/I18n/Translator/Translator.php b/lib/Pi/I18n/Translator/Translator.php
index 2e470a45f5..e96f68dc0a 100644
--- a/lib/Pi/I18n/Translator/Translator.php
+++ b/lib/Pi/I18n/Translator/Translator.php
@@ -57,10 +57,7 @@ class Translator extends ZendTranslator
protected $loader;
/**
- * Set the default locale.
- *
- * @param string $locale
- * @return Translator
+ * {@inheritDoc}
*/
public function setLocale($locale)
{
@@ -72,9 +69,7 @@ public function setLocale($locale)
}
/**
- * Get the default locale.
- *
- * @return string
+ * {@inheritDoc}
*/
public function getLocale()
{
@@ -86,9 +81,7 @@ public function getLocale()
}
/**
- * Get the fallback locale.
- *
- * @return string
+ * {@inheritDoc}
*/
public function getFallbackLocale()
{
@@ -181,12 +174,7 @@ public function getLoader()
}
/**
- * Translate a message.
- *
- * @param string $message
- * @param string|null $textDomain
- * @param string|null $locale
- * @return string
+ * {@inheritDoc}
*/
public function translate($message, $textDomain = null, $locale = null)
{
@@ -198,14 +186,7 @@ public function translate($message, $textDomain = null, $locale = null)
}
/**
- * Translate a plural message.
- *
- * @param string $singular
- * @param string $plural
- * @param int $number
- * @param string|null $textDomain
- * @param string|null $locale
- * @return string
+ * {@inheritDoc}
*/
public function translatePlural(
$singular,
@@ -221,12 +202,7 @@ public function translatePlural(
}
/**
- * Get a translated message.
- *
- * @param string $message
- * @param string $locale
- * @param string $textDomain
- * @return string|null
+ * {@inheritDoc}
*/
protected function getTranslatedMessage(
$message,
@@ -259,7 +235,7 @@ protected function getTranslatedMessage(
*
* @param array|string $domain
* @param string|null $locale
- * @return Translator
+ * @return bool
*/
public function load($domain, $locale = null)
{
@@ -268,7 +244,7 @@ public function load($domain, $locale = null)
$this->setTextDomain($domain[0]);
$this->setLocale($locale);
- $messages = Pi::service('registry')->i18n->setGenerator(array($this, 'loadResource'))->read($domain, $this->locale);
+ $messages = (array) Pi::service('registry')->i18n->setGenerator(array($this, 'loadResource'))->read($domain, $this->locale);
$this->messages[$this->textDomain][$this->locale] = new TextDomain($messages);
//$this->messages[$this->textDomain][$this->locale] = $messages;
if ($this->textDomain && $messages) {
@@ -283,7 +259,8 @@ public function load($domain, $locale = null)
//$this->messages[''][$this->locale] = $messages;
}
}
- return $this;
+
+ return $messages ? true : false;
}
/**
@@ -295,7 +272,19 @@ public function load($domain, $locale = null)
public function loadResource($options)
{
$filename = Pi::service('i18n')->getPath(array($options['domain'], $options['file']), $options['locale']);
- $result = (array) $this->loader->load($options['locale'], $filename);
+ try {
+ $result = $this->loader->load($options['locale'], $filename);
+ } catch (\Exception $e) {
+ $result = false;
+ }
+ if (false === $result) {
+ if (Pi::service()->hasService('log')) {
+ Pi::service()->getService('log')->info(sprintf('Translation "%s-%s.%s" load failed.', $options['domain'], $options['file'], $options['locale']));
+ }
+ $result = array();
+ } else {
+ $result = (array) $result;
+ }
return $result;
}
}
diff --git a/lib/Pi/Log/Formatter/Debugger.php b/lib/Pi/Log/Formatter/Debugger.php
index c16cb6886e..55d62399c3 100644
--- a/lib/Pi/Log/Formatter/Debugger.php
+++ b/lib/Pi/Log/Formatter/Debugger.php
@@ -73,7 +73,7 @@ public function format($event)
}
if (!empty($event['extra']['file'])) {
/**#@++
- * Remove path prefix for security considerations
+ * Remove path prefix for security concerns
*/
$location .= sprintf(' in %s', Pi::service('security')->path($event['extra']['file']));
/**#@-*/
diff --git a/lib/Pi/Log/Logger.php b/lib/Pi/Log/Logger.php
index c1d8143cb5..71fa94e21a 100644
--- a/lib/Pi/Log/Logger.php
+++ b/lib/Pi/Log/Logger.php
@@ -184,6 +184,7 @@ public function getWriters()
{
return $this->writers;
}
+
/**
* Set the writers
*
diff --git a/lib/Pi/Log/Writer/Debugger.php b/lib/Pi/Log/Writer/Debugger.php
index d014ef35a5..9fb060f81a 100644
--- a/lib/Pi/Log/Writer/Debugger.php
+++ b/lib/Pi/Log/Writer/Debugger.php
@@ -47,6 +47,23 @@ class Debugger extends AbstractWriter
'system' => array(),
);
+ protected $muted = false;
+
+ /**
+ * Enable/disable
+ *
+ * @param bool $flag
+ * @return bool return previous muted value
+ */
+ public function mute($flag = true)
+ {
+ $muted = $this->muted;
+ if (null !== $flag) {
+ $this->muted = (bool) $flag;
+ }
+ return $muted;
+ }
+
/**
* get formatter for loggder writer
*
@@ -111,6 +128,9 @@ public function systemInfoFormatter()
*/
protected function doWrite(array $event)
{
+ if ($this->muted) {
+ return;
+ }
if ($event['priority'] > Logger::DEBUG) {
return;
}
@@ -134,6 +154,9 @@ protected function doWrite(array $event)
*/
public function doProfiler(array $event)
{
+ if ($this->muted) {
+ return;
+ }
$message = $this->profilerFormatter()->format($event);
$this->logger['profiler'][] = $message;
@@ -147,6 +170,9 @@ public function doProfiler(array $event)
*/
public function doDb(array $event)
{
+ if ($this->muted) {
+ return;
+ }
$message = $this->dbProfilerFormatter()->format($event);
$this->logger['db'][] = $message;
}
@@ -253,6 +279,9 @@ public function systemInfo()
public function render()
{
+ if ($this->muted) {
+ return;
+ }
$this->systemInfo();
// Use heredoc for log contents
diff --git a/lib/Pi/Mvc/View/Http/ErrorStrategy.php b/lib/Pi/Mvc/View/Http/ErrorStrategy.php
index 215bb5b32e..f606fdf7e5 100644
--- a/lib/Pi/Mvc/View/Http/ErrorStrategy.php
+++ b/lib/Pi/Mvc/View/Http/ErrorStrategy.php
@@ -83,6 +83,10 @@ public function prepareErrorViewModel(MvcEvent $e)
$viewModel = null;
if (!$result instanceof ViewModel) {
$viewModel = new ViewModel;
+ $config = $e->getApplication()->getServiceManager()->get('Config');
+ $viewConfig = $config['view_manager'];
+ $template = isset($viewConfig[$templateName]) ? $viewConfig[$templateName] : 'error';
+ $viewModel->setTemplate($template);
} else {
$viewModel = $result;
}
@@ -96,11 +100,6 @@ public function prepareErrorViewModel(MvcEvent $e)
}
$viewModel->setVariable('code', $statusCode);
- $config = $e->getApplication()->getServiceManager()->get('Config');
- $viewConfig = $config['view_manager'];
- $template = isset($viewConfig[$templateName]) ? $viewConfig[$templateName] : 'error';
- $viewModel->setTemplate($template);
-
$e->setResult($viewModel);
// Inject error ViewModel to root ViewModel in case InjectViewModelListener is not triggered
diff --git a/lib/Pi/Mvc/View/Http/ViewStrategyListener.php b/lib/Pi/Mvc/View/Http/ViewStrategyListener.php
index 78f6aaff14..5279da0676 100644
--- a/lib/Pi/Mvc/View/Http/ViewStrategyListener.php
+++ b/lib/Pi/Mvc/View/Http/ViewStrategyListener.php
@@ -65,8 +65,11 @@ public function attach(Events $events)
$this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER, array($this, 'renderThemeAssemble'), 10000);
$this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'renderThemeAssemble'), 10000);
+ // Canonize ViewModel for error/exception
+ $this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER, array($this, 'canonizeErrorResult'), 10);
+
// Canonize theme layout if necessary
- $this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER, array($this, 'canonizeThemeLayout'), 10);
+ $this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER, array($this, 'canonizeThemeLayout'), 5);
// Complete meta assemble for theme
$this->listeners[] = $events->attach(MvcEvent::EVENT_FINISH, array($this, 'completeThemeAssemble'), 10000);
@@ -107,13 +110,7 @@ public function prepareRequestType(MvcEvent $e)
// Disable error debugging for AJAX and Flash
if ($type) {
- Pi::service('log')->active(false);
- /*
- // Not implemented yet
- if (Pi::service('log')->debugger()) {
- Pi::service('log')->debugger()->disable();
- }
- */
+ Pi::service('log')->mute();
}
}
@@ -266,6 +263,85 @@ public function canonizeActionResult(MvcEvent $e)
$e->setResult($model);
}
+ /**
+ * Inspect the result for erroneous action of JSON/AJAX/Feed
+ *
+ * @param MvcEvent $e
+ * @return void
+ */
+ public function canonizeErrorResult(MvcEvent $e)
+ {
+ $result = $e->getResult();
+ if ($result instanceof Response) {
+ return;
+ }
+ if (!$this->type) {
+ return;
+ }
+
+ $response = $e->getResponse();
+ $statusCode = $response->getStatusCode();
+ if ($statusCode < 400) {
+ return;
+ }
+
+ // Cast controller view model to result viewmodel
+ switch ($this->type) {
+ // For Feed
+ case 'feed':
+ if ($result instanceof FeedModel) {
+ $model = $result;
+ $model->setTemplate('');
+ $model->clearChildren();
+ } else {
+ $variables = array();
+ $options = array();
+ if ($result instanceof ViewModel) {
+ $variables = $result->getVariables();
+ $options = $result->getOptions();
+ } elseif ($result instanceof FeedDataModel) {
+ $variables = (array) $result;
+ $options = array('feed_type' => $result->getType());
+ }
+ $model = new FeedModel($variables, $options);
+ }
+ break;
+ // For Json
+ case 'json':
+ if ($result instanceof JsonModel) {
+ $model = $result;
+ $model->setTemplate('');
+ $model->clearChildren();
+ } else {
+ $variables = array();
+ $options = array();
+ if ($result instanceof ViewModel) {
+ $variables = $result->getVariables();
+ $options = $result->getOptions();
+ }
+ $model = new JsonModel($variables, $options);
+ }
+ break;
+
+ // For AJAX/Flash
+ case 'ajax':
+ // MISC
+ default:
+ if ($result instanceof ViewModel) {
+ $model = $result;
+ $model->setTemplate('');
+ $model->clearChildren();
+ $result = null;
+ } else {
+ $model = new ViewModel;
+ }
+ $model->terminate(true);
+
+ break;
+ }
+ $e->setViewModel($model);
+ }
+
/**
* Inject a template into the ViewModel if none present, to skip Zend native InjectTemplateListener
*
diff --git a/lib/Pi/Navigation/Page/AbstractPage.php b/lib/Pi/Navigation/Page/AbstractPage.php
index 48c36d643b..33e71cd583 100644
--- a/lib/Pi/Navigation/Page/AbstractPage.php
+++ b/lib/Pi/Navigation/Page/AbstractPage.php
@@ -28,16 +28,6 @@
*/
abstract class AbstractPage extends ZendAbstractPage
{
- /**#@+
- * Re-initialize
- * Modified by Taiwen Jiang
- */
- /**
- * {@inheritDoc}
- */
- protected $active = null;
- /**#@-*/
-
/**
* {@inheritDoc}
*/
diff --git a/lib/Pi/Navigation/Page/Mvc.php b/lib/Pi/Navigation/Page/Mvc.php
index 7b3daac460..5b4975547a 100644
--- a/lib/Pi/Navigation/Page/Mvc.php
+++ b/lib/Pi/Navigation/Page/Mvc.php
@@ -30,6 +30,16 @@
*/
class Mvc extends ZendMvcPage
{
+ /**#@+
+ * Re-initialize
+ * Modified by Taiwen Jiang
+ */
+ /**
+ * {@inheritDoc}
+ */
+ protected $active = null;
+ /**#@-*/
+
/**#@+
* Added by Taiwen Jiang
*/
diff --git a/lib/Pi/Navigation/Page/Uri.php b/lib/Pi/Navigation/Page/Uri.php
index ac9bda44c8..c8e4c0ed42 100644
--- a/lib/Pi/Navigation/Page/Uri.php
+++ b/lib/Pi/Navigation/Page/Uri.php
@@ -27,15 +27,18 @@
*/
class Uri extends ZendUriPage
{
+ /**#@+
+ * Re-initialize
+ * Modified by Taiwen Jiang
+ */
+ /**
+ * {@inheritDoc}
+ */
+ protected $active = null;
+ /**#@-*/
+
/**
- * Adds a page to the container
- *
- * This method will inject the container as the given page's parent by
- * calling {@link Page\AbstractPage::setParent()}.
- *
- * @param Page\AbstractPage|array|Traversable $page page to add
- * @return AbstractContainer fluent interface, returns self
- * @throws Exception\InvalidArgumentException if page is invalid
+ * {@inheritDoc}
* @see Pi\Navigation\Navigation::addPage()
* @see Pi\Navigation\Page\Mvc::addPage()
*/
@@ -75,4 +78,28 @@ public function addPage($page)
return $this;
}
+ /**
+ * {@inheritDoc}
+ */
+ public function isActive($recursive = false)
+ {
+ /**#@+
+ * Modified by Taiwen Jiang
+ */
+ //if (!$this->active && $recursive) {
+ if (null === $this->active && $recursive) {
+ foreach ($this->pages as $page) {
+ if ($page->isActive(true)) {
+ $this->active = true;
+ return true;
+ }
+ }
+ $this->active = false;
+ return false;
+ }
+ /**#@-*/
+
+ return $this->active;
+ }
+
}
diff --git a/lib/Pi/Utility/Filter.php b/lib/Pi/Utility/Filter.php
index 54da6e0cb3..a8ddd4b36c 100644
--- a/lib/Pi/Utility/Filter.php
+++ b/lib/Pi/Utility/Filter.php
@@ -225,6 +225,12 @@ public static function fromPost($variable, $filter, $options = null)
* $paramFiltered = _filter('+1234.5', 'float', array('flags' => 'allow_thousand'));
*
*
+ * Filter a value with regexp, only alphabetic and numeric characters are allowed:
+ *
+ * $paramFiltered = _filter($paramRaw, , 'regexp', array('regexp' => '/^[a-z0-9]+$/'));
+ * $paramFiltered = _get($paramName, , 'regexp', array('regexp' => '/^[a-z0-9]+$/'));
+ *
+ *
* Sanitize a value:
*
* $paramSanitized = _sanitize('1234.5', 'int');
diff --git a/lib/Pi/View/Helper/Block.php b/lib/Pi/View/Helper/Block.php
index 15172374db..6f26ec553a 100644
--- a/lib/Pi/View/Helper/Block.php
+++ b/lib/Pi/View/Helper/Block.php
@@ -149,14 +149,6 @@ protected function renderBlock(BlockRow $blockRow, $options = array())
$blockData = null;
if ('tab' != $block['type'] && $block['cache_ttl']) {
$cacheKey = empty($options) ? md5($block['id']) : md5($block['id'] . serialize($options));
- /*
- $cacheKey = 'b' . $cacheKey;
- $cacheOptions = array(
- 'ttl' => $block['cache_ttl'],
- 'namespace' => $block['module'] ?: 'system',
- );
- $blockData = Pi::service('cache')->getItem($cacheKey, $cacheOptions);
- */
$renderCache = Pi::service('render')->setType('block');
$renderCache->meta('key', $cacheKey)
->meta('namespace', $block['module'] ?: 'system')
@@ -179,11 +171,6 @@ protected function renderBlock(BlockRow $blockRow, $options = array())
if (false === $blockData) {
return false;
}
- /*
- if ($cacheOptions) {
- Pi::service('cache')->setItem($cacheKey, json_encode($blockData), $cacheOptions);
- }
- */
if ($renderCache) {
$renderCache->saveCache(json_encode($blockData));
}
diff --git a/lib/Pi/View/Helper/Blocks.php b/lib/Pi/View/Helper/Blocks.php
index 5971270ebf..97786351a9 100644
--- a/lib/Pi/View/Helper/Blocks.php
+++ b/lib/Pi/View/Helper/Blocks.php
@@ -81,14 +81,16 @@ class Blocks extends AbstractHelper
* @var array
*/
protected $zoneMap = array(
- 0 => '1',
- 1 => '2',
- 2 => '3',
- 3 => '4',
- 4 => '5',
- 5 => '6',
- 6 => '7',
- 7 => '8'
+ 0 => '0',
+ 1 => '1',
+ 2 => '2',
+ 3 => '3',
+ 4 => '4',
+ 5 => '5',
+ 6 => '6',
+ 7 => '7',
+ 8 => '8',
+ 99 => '99',
);
/**
diff --git a/usr/locale/README.md b/usr/locale/README.md
new file mode 100644
index 0000000000..964ad0ef78
--- /dev/null
+++ b/usr/locale/README.md
@@ -0,0 +1,42 @@
+Pi Locale
+=========
+
+SPECs
+-----
+* Top folder name as identifier for a language
+* Folder name must be in lowercase
+* Folder name, i.e. language tag must respect [RFC 4646](http://www.ietf.org/rfc/rfc4646.txt)
+* Loacle files are in CSV format except mail templates which are in plain text
+* Both keys and values in CSV must be quoted with double quote (```"```), delimited with comma (```,```)
+* All files must be encoded in the same charset as Pi system, default as ```UTF-8```
+
+Skeleton
+--------
+
+* ```usr/locale```: Global
+ * ```/en```
+ * ```main.csv```: Global, loaded on every request
+ * ```navigation.csv```: Navigation and menu
+ * ```timezone.csv```: Timezone
+ * ...
+ * ```/zh-cn```
+ * ...
+* ```usr/module/demo```: Module ```demo```
+ * ```/en```
+ * ```/mail```: Mail templates
+ * ```mail-template.text```
+ * ```main.csv```: module global, loaded on every request of current module
+ * ```navigation.csv```
+ * ```admin.csv```: Admin area
+ * ```config.csv```: Config edit
+ * ```feed.csv```: Feed
+ * ...
+ * ```/zh-cn```
+ * ...
+* ```usr/theme/default```: Theme ```default```
+ * ```/en```
+ * ```main.csv```: theme global
+ * ```admin.csv```: Admin area
+ * ...
+ * ```/zh-cn```
+ * ...
diff --git a/usr/module/page/src/Route/Page.php b/usr/module/page/src/Route/Page.php
index 31a181765e..3e6e307d53 100644
--- a/usr/module/page/src/Route/Page.php
+++ b/usr/module/page/src/Route/Page.php
@@ -134,7 +134,9 @@ public function assemble(array $params = array(), array $options = array())
if (empty($url)) {
$url = $action;
} elseif (!empty($action)) {
- $url = $action . $this->paramDelimiter . $url;
+ if ($action != 'index') {
+ $url = $action . $this->paramDelimiter . $url;
+ }
} else {
$url = 'view' . $this->paramDelimiter . $url;
}
diff --git a/usr/module/page/template/admin/page-list.phtml b/usr/module/page/template/admin/page-list.phtml
index f5e4930b0a..9947374c32 100644
--- a/usr/module/page/template/admin/page-list.phtml
+++ b/usr/module/page/template/admin/page-list.phtml
@@ -1,6 +1,8 @@
css($this->assetModule('script/system-ui.css','system'));
$this->jQuery();
+ $this->Backbone();
+ $this->js($this->assetModule('script/system-msg.js','system'));
?>
escape($title); ?>
-
+
- -
+
-
escape($page['title']); ?>
- -
-
-
-
-
-
- escape($page['slug']) : ''; ?>
+
+
+
+ - escape($page['slug']) : ''; ?>
+
-
+
- -
+
-
escape($page['title']); ?>
- -
-
-
-
-
-
- escape($page['slug']) : ''; ?>
+
+
+
+ - escape($page['slug']) : ''; ?>
+
@@ -56,64 +60,40 @@
\ No newline at end of file
diff --git a/usr/module/system/asset/script/system-msg.js b/usr/module/system/asset/script/system-msg.js
index 9d52192547..417088b527 100755
--- a/usr/module/system/asset/script/system-msg.js
+++ b/usr/module/system/asset/script/system-msg.js
@@ -1,10 +1,10 @@
var systemMessage = {
- tmp: '{msg}',
- _css: '.system-layer {position: fixed;top: -21px;height: 20px;line-height: 20px;z-index: 13;width: 100%;text-align: center;}.system-layer-show {top: 0;}.system-layer-msg {margin-left: 10px;}',
+ tmp: '' +
+ '' +
+ '{msg}' +
+ '',
_init: function() {
- var b = document.body;
- $('