API documentationTable Of Contents |
Welcome to PhalconEye’s documentation!¶PhalconEye CMS is based on Phalcon PHP Framework. It contains basic building blocks for rapid development: modules, widgets, plugins and themes. Originally, it has been designed to help web developers kickstart their projects, and therefore it is more a development platform then general purpose Content Management Systems unlike others you might already be familiar with such as Joomla or Wordpress. What is Phalcon?¶Phalcon is an open source, full stack framework for PHP 5 written as a C-extension, optimized for high performance. You don’t need to learn or use the C language, since the functionality is exposed as PHP classes ready for you to use. Phalcon also is loosely coupled, allowing you to use its objects as glue components based on the needs of your application. Phalcon is not only about performance, our goal is to make it robust, rich in features and easy to use! Table of Contents¶Benchmarks¶Note that benchmarks can be different... this depends on cache system/settings, PC power, software, PHP version, etc... The testing hardware environment:
Software:
Installation¶
<VirtualHost *:80>
ServerAdmin admin@mail.com
ServerName test.local
DocumentRoot /www/phalconeye/www/public
ErrorLog /www/phalconeye/logs/errors.log
CustomLog /www/phalconeye/logs/access.log combined
<Directory "/www/phalconeye/www/public">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
User’s guide¶Working With A Grid¶Grid system allows fast and simple browsing of database rows. Let’s take a look at main components of the grid: ![]() Components description:
User management¶The CMS enables you to create, edit, delete and browse users in a very simple manner. Browsing users is implemented via a grid system (built into the CMS): ![]() To add a new user simply go to “Create new user” from top navigation bar and fill in the form: ![]() Fields description:
To delete user - search for appropriate record via the grid and click “Delete” link located in “Actions” column. You will be asked to confirm this action Role management¶PhalconEye enables Administrators to define privileged groups of users. ![]() To add a new Role go to “Create new role” from top navigation bar and fill-in the form: ![]() Fields description:
By default PhalconEye comes with 3 system Roles:
These system Roles can not be deleted as opposed to new Roles created by Administrators, which can be removed via grid system. Dynamic pages¶Dynamic pages allow Administrators to chose a layout from 12 pre-defined sets and add content without the need for knowledge of any programming language. ![]() You can see the type of layout for each page in “Layout” column. Adding and editing pages¶To create a new page - navigate to “Create new page”. ![]() Fields description:
Header and Footer are two specific “Page areas” in the CMS for whose the above settings can not be edited. These as well as the “Home page” are integrated into the CMS and cannot be removed. Page management¶Pages consist of widgets which are the basic build blocks in PhalconEye that perform a specific function such as displaying Menu or Social Icons. You will find widgets in WordPress, Joomla and Drupal call them modules. Widgets can be easily dragged and dropped into a specific widget area within the layout. ![]() List of available widgets is located on the left of management page. Widgets can be part of modules and be displayed under appropriate module name (eg. Menu and Header widgets belong to Core module). They can also be installed as standalone packages and will be displayed under “Other” (see Recentblogs widget above). Right next to list of available widget you can see the layout you have chosen for your page - this is the drop area for widgets. Adding new widget - Simply drag a widget from the left area and drop it onto any part of the layout. It is also possible to re-arrange widgets which have already been dropped by dragging and dropping them elsewhere. Remove widget - Simply click ‘X’ link from within the widget. Edit widget - Almost all widgets have their options. To edit them, click ‘Edit’ link within the widget and you will see a form with options: ![]() Once you have done editing widget’s parameters, “Save” button becomes active. Note that you can configure multiple widgets at the same time, but all the changes will take effect only if you save the layout: ![]() Page layout can be changed at any time: ![]() Be careful, though, when doing so! When a newly chosen layout has less columns than current, some of them might be lost permanently with all its widgets and their saved parameters. To avoid that you can temporarily move elsewhere widgets from the column which is about to be removed. @TODO: Describe precedence of which columns are removed. Languages¶This area enables Administrators to create custom translations for elements on the website such as buttons, commands, alerts and so on... ![]() Adding a language¶To add a new language - click “Create new language” from top navigation. ![]() Fields description:
Performance note¶Custom translations are kept in database for simplicity of development. In production mode, however, translations are compiled into a php file to avoid database performance overhead. To re-compile available translations click “Compile languages” and wait until it completes. Export¶![]() You can easily export a language you have created into a JSON file and import it into another instance of PhalconEye. Scope defines logical separation of the language to export only certain part of translations (e.g. Blog - only translations used in Blog module will be exported). Import¶Enables administrators to import translations for other languages. Simply, click “Import” and select a JSON file file with translations. After short period of time translations will be imported and you will see message about the result. Once finished, do not forget to compile them! Manage Translations¶You can manage translation by clicking “Manage” within a row. ![]()
Wizard¶Spped up translations with wizard! It will give you the original text, translation and a suggestion. The suggestion is automated on en -> current_language by Yandex translation API. By clicking “Next” button you will save the translation field to database. ![]() File management¶File management is implemented via Pydio software: ![]() You can read more about Pydio in official documentation. System settings¶![]() Fields description:
Performance settings¶Performance form allows to setup some improvements in speed. ![]() Fields description:
If “File” adapter is selected:
If “Memcached” is selected:
If “APC” adapter has no additional settings. If “Mongo” adapter is selected:
Access Rights¶Access Rights allows to control access restrictions for specific areas of the CMS. These Resources and their access policies may be defined by modules. By default, the Core module comes with two Resources:
![]() CoreModelPage Resource has two configurable actions: ![]()
These actions can be assigned individually to each Role, (“Admin” Role in this case) visible in the top right corner. To make it clear look at another example: ![]() Here, defined backend access rights for role “Admin” - it will let Admin users to access the Administration area. Texts like “ACTION_ADMINAREA_ACCESS_DESCRIPTION” are translation placeholders and can be changed in language management system. Developer’s guide¶CMS Structure¶Let’s look on project structure... PhalconEye
├── app // Application source code.
│ ├── config // Main configuration directory.
│ │ ├── development
│ │ └── production
│ ├── engine // Engine library directory, heart of PhalconEye.
│ │ ├── Api
│ │ ├── Asset
│ │ │ └── Css
│ │ ├── Behaviour
│ │ ├── Cache
│ │ ├── Console
│ │ │ └── Command
│ │ ├── Db
│ │ │ └── Model
│ │ ├── Exception
│ │ ├── Form
│ │ │ ├── Behaviour
│ │ │ ├── Element
│ │ │ └── Validator
│ │ ├── Grid
│ │ │ ├── Behaviour
│ │ │ └── Source
│ │ ├── Helper
│ │ ├── Package
│ │ │ ├── Exception
│ │ │ ├── Model
│ │ │ └── Structure
│ │ ├── Plugin
│ │ ├── Translation
│ │ ├── View
│ │ └── Widget
│ ├── libraries // Directory for packages with type "library", that can be installed.
│ ├── modules // Directory for "module" packages.
│ │ ├── Core // Required module: Core.
│ │ │ ├── Api
│ │ │ ├── Assets
│ │ │ ├── Command
│ │ │ ├── Controller
│ │ │ ├── Form
│ │ │ ├── Helper
│ │ │ ├── Model
│ │ │ ├── View
│ │ │ └── Widget
│ │ └── User // Required module: User.
│ │ ├── Controller
│ │ ├── Form
│ │ ├── Helper
│ │ ├── Model
│ │ ├── View
│ │ └── Widget
│ ├── plugins // Plugins directory (packages).
│ ├── var // This is work directory, contains: logs, cache, packages operation results, languages, etc.
│ │ ├── cache
│ │ │ ├── annotations
│ │ │ ├── languages
│ │ │ ├── metadata
│ │ │ └── view
│ │ ├── logs
│ │ └── temp
│ └── widgets // Widgets directory (packages).
└── public // Public directory, this directory can be accessed through internet, must be set as www root.
├── assets
└── themes
We can separate project on two logical parts: App directory¶Configuration¶This directory contains different configuration stages. More info you can find in other section of documentation. Engine¶Engine directory is heart of CMS. Here under the hood main horsepower =). Libraries¶Libraries that can be installed via packages system. Read more information about packages in other section. Modules¶Modules are also part of packages. But here we can find default two modules: Core and User. Core module contains code for general classes of CMS. Also it implements admin panel logic.
User module pointed on work with users.
Plugins¶One more type of package. Plugins exist for defining additional logic by system events (events handlers). Var¶Var directory is for main garbage =). Contains: logs, temp files (that currently processing), system data (metadata about packages), cache. Public directory¶As normal practice public directory must be defined as WWW_ROOT. Public directory allows to store static files like assets and user files. Here we can find some directories by default:
Assets¶Assets files could be any files required by your application (css, js, images, etc). This directory is controlled by CMS. Don’t try to copy there any of your files. Assets files installing to this directory directly from modules (each module has it’s own directory with Assets files). Files under modules can’t be accessed via http, so, they are installed here for farther usage. External¶This directory for external libs and programs. Here we can find jQuery files, Bootstrap css and other. Files¶This directory used for different public files. Also it’s used by Ajaxplorer tool. Configuration¶Stages¶Stages allow you to set different configuration per virtual host, server, etc. By default, PhalconEye comes with two stages: “development” and “production”. CMS is bundled with “development” stage, this is defined in ‘/public/.htaccess’ file: SetEnv PHALCONEYE_STAGE development
If a stage isn’t defined “production” will be used. You are allowed to add as many stages as you want, just create new directory inside ‘config’ folder and set default configuration. Config files¶Configuration contains two default files, which are necessary for the system:
<?php
return array(
'debug' => true, // Use debug mode?
'profiler' => true, // Show profiler for admins?
'baseUrl' => '/', // Base site url.
'cache' =>
array(
'lifetime' => '86400',
'prefix' => 'pe_',
'adapter' => 'File',
'cacheDir' => ROOT_PATH . '/app/var/cache/data/',
),
'logger' =>
array(
'enabled' => true,
'path' => ROOT_PATH . '/app/var/logs/',
'format' => '[%date%][%type%] %message%',
),
'view' =>
array(
'compiledPath' => ROOT_PATH . '/app/var/cache/view/',
'compiledExtension' => '.php',
'compiledSeparator' => '_',
'compileAlways' => true,
),
'session' =>
array(
'adapter' => 'Files',
'uniqueId' => 'PhalconEye_',
),
'assets' =>
array(
'local' => 'assets/', // Local assets location.
'remote' => false, // This can be used for your CDN.
),
'metadata' =>
array(
'adapter' => 'Files',
'metaDataDir' => ROOT_PATH . '/app/var/cache/metadata/',
),
'annotations' =>
array(
'adapter' => 'Files',
'annotationsDir' => ROOT_PATH . '/app/var/cache/annotations/',
)
);
<?php
return array(
'adapter' => 'Mysql',
'host' => 'localhost',
'port' => '3306',
'username' => 'root',
'password' => NULL,
'dbname' => 'phalconeye',
);
Behaviour¶All *.php files under stage directories are merged into one structure. For example we can have the following files: .
└─── development
├── application.php
├── yourconfig.php
├── yourconfig2.php
└── database.php
It means that in “development” stage we would be able to get the values as follows: <?php
$config = $this->getDI()->getConfig();
// Get debug mode.
$isDebug = $config->application->debug;
// Get database adapter.
$adapter = $config->database->adapter;
// Get custom config value.
$someValue = $config->yourconfig->someValue;
If current stage isn’t “development” merged configuration will be cached in /app/var/cache/data/config.php file. Packages¶Package allows to create modular functionality and share it with community or just use in flexible projects that you can develop. Package can be exported from system as zip archive and will have manifest file and directory with source code, for example, module: .
├── package
│ ├── Assets
│ ├── Command
│ ├── Controller
│ ├── Form
│ ├── Helper
│ ├── Model
│ ├── Plugin
│ ├── View
│ ├── Widget
│ ├── Bootstrap.php
│ └── Installer.php
└── manifest.json
Manifest file is required for information about package: {
"type": "module", // Package type.
"name": "blog", // Package unique identity (name).
"title": "Blog", // Package title.
"description": "PhalconEye Blog module.", // Package description.
"version": "0.1.0", // Version, allowed two types of versioning: x.x.x and x.x.x.x
"author": "PhalconEye Team", // Who is the alpha and the omega of this package?
"website": "http:\/\/phalconeye.com\/", // Website address.
"dependencies": [ // Dependencies, allows you to set some relation to other packages, to make sure that your package will work as follows.
{
"name": "core",
"type": "module",
"version": "0.4.0"
},
{
"name": "user",
"type": "module",
"version": "0.4.0"
}
],
"events": [ /*
Events, that must be attached to EventManager.
Usage: Namespace\ClassName = eventName
Note: class Blog\Plugin\SomePluginDir\SomeName must be located in related directory and has correct name and namespace.
*/
"Blog\\Plugin\\SomePluginDir\\SomeName=dispatch:beforeDispatchLoop",
"Blog\\Plugin\\Core=init:afterEngine"
],
"widgets": [ // Widgets that related to this module.
{
"name": "recentblogs", // Widget unique name.
"module": "blog", // Widget related to module, it's name.
"description": "Recent blogs", // Description of this widget.
"is_paginated": "1", // Flag: is there are any pagination?
"is_acl_controlled": "1", // Flag: must be controlled by ACL? Adds multiselect box for widget options, that allows to select allowed roles.
"admin_form": null, /*
Admin form that can control and display widget options, can be:
null - no options or default,
"action" - widget controller has adminAction method,
"Some/Namespace/Form/ClassName" - Form class that must be rendered.
*/
"enabled": true // Is enabled.
}
],
"i18n": [ // Localization for this module.
{ // Separated by packages (different languages).
"info": "PhalconEye Language Package", // Language package short info.
"version": "0.4.0", // System version.
"date": "28-Apr-2014 21:10", // Date of this package.
"name": "English", // Language name.
"language": "en", // Language unique identification.
"locale": "en_US", // Language locale.
"content": { // Content of language (it's translations).
"blog": {
"Home": "SweetHome" // Key : Value (Original : Translated).
}
}
},
{
"info": "PhalconEye Language Package",
"version": "0.4.0",
"date": "28-Apr-2014 21:10",
"name": "German",
"language": "de",
"locale": "de_DE",
"content": {
"blog": {
"Home": "Zuhause"
}
}
}
]
}
Package Manager¶Package manager allows you to create, edit, delete, export packages that you are developing.
As you can see from image, there are several types of package:
![]() Simple “HOW TO”: Create new package¶If you need to create new package, you can use PhalconEye tool - Package manager. It will allow you to create structured package. Go to Admin panel -> Packages (left sidebar) -> Create new package (top menu). Fill form with your data: ![]() Form data:
If you will select package type “Widget” you will see that form has additional fields: ![]() Additional data:
Upload existing package¶Upload (Installation) is very simple. Just select your package (*.zip file) and push “Upload” button. If package is broken or your system has no required dependencies error message will be shown. ![]() Edit, disable, uninstall package¶Assume, that you have created new package and now you need to change some information (for e.g. version) or you need to create *.zip package from it. You can edit information about your package. Edit form is limited, but you can change “meta” info about it. ![]() You can disable package, it is possible from list. Go to Package manager -> <Package type> -> Find it in list and click “Disable”/”Enable” button. There are some limits for this functionality:
To uninstall your package, please use Package manager or you risking to broke you system. Go to Package manager -> <Package type> -> Find it in list and click “Uninstall” button. If package has dependencies system will not allow you to uninstall it. Export package¶Assume, that you has finished your package and want to distribute it. Go to Package manager -> <Package type> -> Find it in list and click “Export” button. ![]() Export form has some fields:
Package types¶There are several types of packages, let’s look on each of them: Module Package¶Modules - is main package type. It can be represented as standalone subsystem and can hold widgets, plugins, libraries, but not themes. .
├── package
│ ├── Api
│ ├── Assets
│ ├── Command
│ ├── Controller
│ ├── Form
│ ├── Helper
│ ├── Model
│ ├── Plugin
│ ├── View
│ ├── Widget
│ ├── Bootstrap.php
│ └── Installer.php
└── manifest.json
Let’s look an all aspects of module structure: This API just like a DI wrapper that allows to create API classes at module level. You can access to known API using module key in DI. For example: Core module has class Core\Api\Acl that can be accessed in such way: <?php
// Note: getObject is a method of class Core\Api\Acl
// In controller (or in any Phalcon\DI\Injectable):
$this->core->acl()->getObject($id);
// In any other place:
$this->getDI()->get("core")->acl()->getObject($id);
// Structure:
$this->getDI()->get("MODULE_NAME")->API_CLASS()->API_CLASS_METHOD
By the way... all object’s that were accessed stay at DI with key of it’s namespace... <?php
$this->core->acl()->getObject($id);
// Note that "Core\Api\Acl" key will be in DI only if you call it special API wrapper, by default it's not initialized.
// So this like a cache usage without wrapper, in such way you can trigger when your API was used.
$this->getDI()->get("Core\Api\Acl")->getObject($id);
Assets directory goal - all module must have it’s own frontend styles and visualization, so Assets must have (required) 3 directories: css, img, js. This directories is using as assets source for overall assets compilation. /app/modules/Core/Assets
├── css
│ ├── admin
│ ├── install.less
│ └── profiler.less
├── img
│ ├── admin
│ ├── grid
│ ├── loader
│ ├── misc
│ ├── pe_logo.png
│ ├── pe_logo_white.png
│ └── profiler
├── js
│ ├── admin
│ ├── core.js
│ ├── form
│ ├── form.js
│ ├── i18n.js
│ ├── pretty-exceptions
│ ├── profiler.js
│ └── widgets
└── sql
└── installation.sql
If you will look at public directory you will find “assets” directory there. This directory is accessible through web server. It contains all assets collected from installed modules, for example, if we have 3 modules core, user and blog we will have such structure: public
└── css
├── core
│ └── somestyle.css // Originally this file is located at /app/modules/Core/Assets/css/somestyle.css
├── user
│ ├── somedir
│ │ └── someotherstyle.css // This file is located at /app/modules/User/Assets/css/somedir/someotherstyle.css
│ └── somestyle.css
├── blog
│ └── somestyle.css
├── constants.css // This files located at /public/themes/<current theme>/*.less(*.css)
└── theme.css
On this example (css only included) we can see structure after assets installation (collection). This files copied from modules Assets directory with directory tree structure, this work only for directories located in Assets and named as “css”, “img”, “js”. Other directories doesn’t affected by assets system, so if you will have directories like “sql” or “data” in Assets - they will not be copied to /public/assets directory. Assets can be installed from console, using command: “php public/index.php assets install”. You can read about commands manager and console usage in this documentation. Also assets can be installed via “debug” switcher, when you enabling or disabling “debug” flag - system cleans cache and installs new assets.
This directory contains commands classes, that can be used in console. You can read more about commands in commands manager section. Controller directory contains all module controllers, request handlers. You can read how to use them at Phalcon documentation. Contains all forms classes, usually it can have such structure: /app/modules/User/Form/
├── Admin
│ ├── Create.php
│ ├── Edit.php
│ ├── RoleCreate.php
│ └── RoleEdit.php
└── Auth
├── Login.php
└── Register.php
How to create and use forms you can read in special section of this documentation. Usually helpers oriented as View helpers, but you can use them everywhere in your code (where DI is available). Read about more helpers. Plugins used as event handlers. Each plugin can have attached event handlers to class. Read about plugins. This directory contains views. There are directories with controller name inside which you can find views with extension (*.volt). Here some useful links about views:
If you will look at Core module, you will find 3 widget there: /app/modules/Core/Widget/
├── Header
│ ├── Controller.php
│ └── index.volt
├── HtmlBlock
│ ├── Controller.php
│ └── index.volt
└── Menu
├── Controller.php
└── index.volt
Each directory inside “Widget” directory - is independent widget. Widget has it’s own controller and one or several views (depends on actions, that you need inside controller). Read more about widgets. Installer is a script that allows to do some actions per installation/update or removal. Let’s look on example (Core installer): <?php
use Engine\Installer as EngineInstaller;
use Phalcon\Acl as PhalconAcl;
class Installer extends EngineInstaller
{
const
/**
* Current package version.
*/
CURRENT_VERSION = '0.4.0';
/**
* Used to install specific database entities or other specific action.
*
* @return void
*/
public function install()
{
$this->runSqlFile(__DIR__ . '/Assets/sql/installation.sql');
}
/**
* Used before package will be removed from the system.
*
* @return void
*/
public function remove()
{
}
/**
* Used to apply some updates.
* Return 'string' (new version) if migration is not finished, 'null' if all updates were applied.
*
* @param string $currentVersion Current module version.
*
* @return string|null
*/
public function update($currentVersion)
{
return null;
}
}
As you can see, installer has 3 mandatory methods: install, remove, update.
For example, if current installed version is 1.0.0 and new package is 1.2.1. At 1.1.0 and 1.2.0 were database changes that you want to trigger correctly. Method “update” can look like this: <?php
class Installer extends EngineInstaller
{
const
/**
* Current package version.
*/
CURRENT_VERSION = '1.2.1';
public function update($currentVersion)
{
switch($currentVersion){
case "1.1.0"
// Apply database changes from 1.0.0 to 1.1.0.
... CODE HERE...
return "1.2.0";
break;
case "1.2.0"
// Apply database changes from 1.2.0 to 1.2.1.
... CODE HERE...
return CURRENT_VERSION;
break;
}
return null;
}
}
Bootstrap initialize module systems and can adds some services to DI for other modules.
<?php
namespace Core;
use Core\Model\Language;
use Core\Model\LanguageTranslation;
use Core\Model\Settings;
use Core\Model\Widget;
use Engine\Bootstrap as EngineBootstrap;
use Engine\Cache\System;
use Engine\Config;
use Engine\Translation\Db as TranslationDb;
use Phalcon\DI;
use Phalcon\DiInterface;
use Phalcon\Events\Manager;
use Phalcon\Mvc\View\Engine\Volt;
use Phalcon\Mvc\View;
use Phalcon\Translate\Adapter\NativeArray as TranslateArray;
use User\Model\User;
/**
* Core Bootstrap.
*
* @category PhalconEye
* @package Core
* @author Ivan Vorontsov <ivan.vorontsov@phalconeye.com>
* @copyright 2013-2014 PhalconEye Team
* @license New BSD License
* @link http://phalconeye.com/
*/
class Bootstrap extends EngineBootstrap
{
/**
* Current module name.
*
* @var string
*/
protected $_moduleName = "Core";
/**
* Bootstrap construction.
*
* @param DiInterface $di Dependency injection.
* @param Manager $em Events manager object.
*/
public function __construct($di, $em)
{
parent::__construct($di, $em);
/**
* Attach this bootstrap for all application initialization events.
*/
$em->attach('init', $this);
}
/**
* Init some subsystems after engine initialization.
*/
public function afterEngine()
{
$di = $this->getDI();
$config = $this->getConfig();
$this->_initI18n($di, $config);
if (!$config->installed) {
return;
}
// Remove profiler for non-user.
if (!User::getViewer()->id) {
$di->remove('profiler');
}
// Init widgets system.
$this->_initWidgets($di);
/**
* Listening to events in the dispatcher using the Acl.
*/
if ($config->installed) {
$this->getEventsManager()->attach('dispatch', $di->get('core')->acl());
}
// Install assets if required.
if ($config->application->debug) {
$di->get('assets')->installAssets(PUBLIC_PATH . '/themes/' . Settings::getSetting('system_theme'));
}
}
/**
* Init locale.
*
* @param DI $di Dependency injection.
* @param Config $config Dependency injection.
*
* @return void
*/
protected function _initI18n(DI $di, Config $config)
{
$translate = ...
// SOME CODE HERE
$di->set('i18n', $translate);
}
/**
* Prepare widgets metadata for Engine.
*
* @param DI $di Dependency injection.
*
* @return void
*/
protected function _initWidgets(DI $di)
{
if ($di->get('app')->isConsole()) {
return;
}
$widgets = ...
// SOME CODE HERE
$di->get('widgets')->addWidgets($widgets);
}
}
As you can see, bootstrap also can be attached to system events, to handle additional logic. Plugins¶Plugins is just like an event handlers. When you need some additional hooks - you can create plugins. Plugins can be as part of module and as external package. Let’s look on example: <?php
class DispatchErrorHandler
{
/**
* Before exception is happening.
*
* @param Event $event Event object.
* @param Dispatcher $dispatcher Dispatcher object.
* @param PhalconException $exception Exception object.
*
* @throws \Phalcon\Exception
* @return bool
*/
public function beforeException($event, $dispatcher, $exception)
{
// Handle exceptions.
// Some other code...
return true;
}
}
This example from core (EnginePluginDispatchErrorHandler) and attached to system as: <?php
$eventsManager->attach("dispatch:beforeException", new DispatchErrorHandler());
That means, that you can attach your plugins manually. If you want to automate you plugin (and make it editable), you can use Package Manager. It has events editor. ![]() Events manager allowed for modules and plugins package types. In left field you enter event name, in right field - class with namespace that must handle this event. You can use both formats of event naming (dispatch or dispatch:eventName). Read more about events system at Phalcon documentation. Widgets¶Widgets are main components of your content! Widget is component that has it’s own controller and view. It can be placed on page and rendered by request. ./Widget/
└── HtmlBlock
├── Controller.php
└── index.volt
Widgets can be part of some module or as external package. By default “index” action is used, but you can render widget with another action using widget wrapper - EngineWidgetElement->render(“update”). Let’s look on Controller example: <?php
class Controller extends Engine\Widget\Controller
{
/**
* Index action.
* This action is rendered by default.
*
* @return void
*/
public function indexAction()
{
// Get parameter provided by PhalconEye system.
// This parameters handled by admin panel, when enduser can place his widget and setup some params.
$param = $this->getParam('count');
// Get all params for this widget.
$param = $this->getAllParams();
// Engine\Widget\Controller is extended from Phalcon controller and it has DI services.
// So you can use anything from DI.
// As for example - get param from HTTP request.
$queryParam = $this->request->get("param");
// Access to DI.
$di = $this->getDI();
// Set View params.
$this->view->someParam = 1;
// Set widget title, it's automatically takes from form params (added by enduser), but you can override it.
$this->view->title = "Some Title";
// Set flag, that widget has no content and it's wrapper must not be rendered.
return $this->setNoRender();
}
/**
* This action is used, when user requests widgets options at page layout management.
*
* @return CoreForm
*/
public function adminAction()
{
return new YourWidgetFormClass();
}
/**
* Cache this widget?
* This method for wrapper, that will check, if you widget content must be cached.
*
* @return bool
*/
public function isCached()
{
// If this method exists in widget controller
// and returns "true" - rendered content of widget's view will be cached and used at next time.
return true;
}
/**
* Get widget cache key.
*
* @return string|null
*/
public function getCacheKey()
{
// You can use this method to specify your widget cache.
// By default system generates unique cache key automatically.
// Note that this method will not be used if "isCached" method doesn't returns "true".
return "some_you_unique_key";
}
/**
* Get widget cache lifetime.
*
* @return int
*/
public function getCacheLifeTime()
{
// Specify cache life time for your widget's cache.
// 300 - is by default.
return 300;
}
}
Let’s look on index.volt example: {% extends "../../Core/View/layouts/widget.volt" %}
{% block content %}
{{ html }}
{% endblock %}
Block “content” is main widget block. Here you can use usual volt template features.
Widget params can be configured at widget options form at admin panel. Go to admin panel -> Pages -> Push on “Manage” link for some page -> find widget in layout (or place a new one) and push “Edit” link.
How default widget form looks (when all options allowed, title - is always present, by default): ![]() Configure your own admin form you can in 2 ways: 1) At widget creation (or in database, field - “admin_form”) set to “action”. This will triggers “actionIndex” in widget controller. This action must return CoreForm instance, that will be rendered at admin panel (As example, look at HtmlBlock widget code). 2) At widget creation (or in database, field - “admin_form”) set to “SomeNamespaceFormClass”. This will create instance of “SomeNamespaceFormClass” when the user will request widget admin form. Libraries¶Libraries - your code (or vendor) for some specific tasks. For example, you will need mail library. You can use existing mail libraries (and add your wrapper) or write your own. Let’s say, you will use SwiftMailer (or other, doesn’t matter). Library structure can be like this: ./libraries/
└── Mail
├── vendor // Here you can put your SwiftMailer.
└── Wrapper.php // Wrapper written by you.
In that case, if you will create such library, your can access it via Mail\Wrapper::factory(). It means, that all classes inside subdirectories of libraries directory will be places in namespace of it’s subdirectory name: ./libraries/
├── Mail
│ ├── ..
│ └── Wrapper.php // Mail\Wrapper::factory()
└── Some
├── New
│ └── Class // Some\New\Class::factory()
└── Class.php // Some\Class::factory()
Note: “factory” method is just for example, you can use any structure you want inside your library. Themes¶Theme package - archive with *.less, *.css and images. Respects folder structure. Theme must include constants.less and theme.less files. Other *.less files you can separate them via LESS compiler logic. All *.less files inside themes compiles automatically. Constants files is required to handle style structure for modules, that can use this style manners for their html. Constants from constants.less can be used in your modules and widgets to allow them use current style respecting theme changing by end user. Models¶Models is database entities. You can read more about them in Phalcon documentation. But PhalconEye has little difference in models: annotations, changed methods (get, getFirst), etc. Example of model: <?php
namespace Core\Model;
use Engine\Db\AbstractModel;
/**
* Access.
*
* @category PhalconEye
* @package Core\Model
* @author Ivan Vorontsov <ivan.vorontsov@phalconeye.com>
* @copyright 2013-2014 PhalconEye Team
* @license New BSD License
* @link http://phalconeye.com/
*
* @Source("access")
* @BelongsTo("role_id", "User\Model\Role", "id")
*/
class Access extends AbstractModel
{
/**
* @Primary
* @Identity
* @Column(type="string", nullable=false, column="object", size="55")
*/
public $object;
/**
* @Primary
* @Column(type="string", nullable=false, column="action", size="255")
*/
public $action;
/**
* @Primary
* @Column(type="integer", nullable=false, column="role_id", size="11")
*/
public $role_id;
/**
* @Column(type="string", nullable=true, column="value", size="25")
*/
public $value;
}
Maybe you noticed that access to model fields possible through public modifier of this fields, but not through public methods. This was made coz of database fields naming conversion. In that case you can fast search through code and understand what field is responsible for. If you don’t like this idea - you can change AbstractModel and add your own magic method (__call) or add methods to your model. Annotations¶Annotations are needed as metadata and for schema generation feature.
Methods¶Here is some useful methods: <?php
// Get table name in database (for queries and other stuff).
$tableName = Access::getTableName();
// Methods "get" and "getFirst" was modified with sprintf method inside.
// Parameters:
// 1. Condition with placeholders
// 2. Parameter for condition placeholders.
// 3. Order by field.
// 4. Limit.
$access1 = Access::get('action = "%s" AND value = "%s"', ["edit", "allowed"], "role_id", 100)
$access2 = Access::getFirst('module = "%s" AND name = "%s"', ["edit", "allowed"], "role_id", 100)
// Create builder for access table, returns object of Phalcon\Mvc\Model\Query\Builder class. First param - table alias in query builder.
$builder = Access::getBuilder("tableAlias");
// Get row identity.
$id = $access1->getId();
Views¶
Volt engine is used as main rendering processor. Views can be found in modules and widgets. Widget views¶Widget can have one or several views, depends on it’s controller actions. By default: index.volt. This view will be used for indexAction of widget controller. Module views¶Module views located under directory “View”. Dispatcher resolves view according to controller and it’s action name. For example, if you have “SomeController” and “newAction” method view for this action must be placed under /View/Some/new.volt (sensitive register). Core module has default layouts for admin and main page layout, to use them, you must add “extends” modifier:
{% extends "../../Core/View/layouts/main.volt" %} {# Main layout, used for all frontend views. #}
{% extends "../../Core/View/layouts/admin.volt" %} {# Admin layout, used in admin views. #}
{% extends "../../Core/View/layouts/widget.volt" %} {# Widget layout, used in widgets views. #}
Helpers¶You can read more about them in other part about helpers. You can use helpers inside your views, this allows to move server logic to php, outside from view part. {{ helper('setting', 'core').get('system_title', '') }} {# Get setting 'system_title', with default value ''. #}
First parameter is helper class name (in that case, this will be Setting.php. Second parameter is namespace of this helper, by default this is ‘engine’, in that example - ‘core’. It means, that this class is accessible at CoreHelperSetting. After that call helper system returns your an object of CoreHelperSetting, this object created only once and by other calls it taken from cache (by singleton logic). Cache in that case is DI, so you also can check if helper is loaded by accessing it in DI: <?php
$di->has('Core\Helper\Setting');
Extension¶View has some additional methods and filters. {# call php code: #}
{{ php('phpinfo()') }}
{{ helper('formatter').formatNumber(100, php('\NumberFormatter::DECIMAL')) }}
{# classof (get_class in php): #}
{% if classof(element) is 'FieldSet' %}
...
{% endif %}
{# instanceof in php: #}
{% if instanceof(element, 'Engine\Form\FieldSet') %}
...
{% endif %}
{# resolveView, allows to find relative path to view. First parameter is view name, second - module name: #}
{{ partial(resolveView('partials/paginator', 'core')) }}
{# i18n filter, for translations: #}
{{ "Some text" | i18n }}
Forms¶Forms allows to handle user data. Forms contains such features:
Form example: <?php
namespace Core\Form\Admin\Setting;
use Core\Form\CoreForm;
/**
* Performance settings.
*
* @category PhalconEye
* @package Core\Form\Admin\Setting
* @author Ivan Vorontsov <ivan.vorontsov@phalconeye.com>
* @copyright 2013-2014 PhalconEye Team
* @license New BSD License
* @link http://phalconeye.com/
*/
class Performance extends CoreForm
{
/**
* Initialize form.
*
* @return void
*/
public function initialize()
{
$this->setTitle('Performance settings');
$this->addContentFieldSet()
->addText('prefix', 'Cache prefix', 'Example: "pe_"', 'pe_')
->addText(
'lifetime',
'Cache lifetime',
'This determines how long the system will keep cached data before
reloading it from the database server.
A shorter cache lifetime causes greater database server CPU usage,
however the data will be more current.',
86400
)
->addSelect(
'adapter',
'Cache adapter',
'Cache type. Where cache will be stored.',
[
0 => 'File',
1 => 'Memcached',
2 => 'APC',
3 => 'Mongo'
],
0
)
/**
* File options
*/
->addText('cacheDir', 'Files location', null, ROOT_PATH . '/app/var/cache/data/')
/**
* Memcached options.
*/
->addText('host', 'Memcached host', null, '127.0.0.1')
->addText('port', 'Memcached port', null, '11211')
->addCheckbox('persistent', 'Create a persistent connection to memcached?', null, 1, true, 0)
/**
* Mongo options.
*/
->addText(
'server',
'A MongoDB connection string',
null,
'mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]]'
)
->addText('db', 'Mongo database name', null, 'database')
->addText('collection', 'Mongo collection in the database', null, 'collection')
/**
* Other.
*/
->addCheckbox('clear_cache', 'Clear cache', 'All system cache will be cleaned.', 1, false, 0);
$this->addFooterFieldSet()->addButton('save');
$this->addFilter('lifetime', self::FILTER_INT);
$this->_setConditions();
}
/**
* Validates the form.
*
* @param array $data Data to validate.
* @param bool $skipEntityCreation Skip entity creation.
*
* @return boolean
*/
public function isValid($data = null, $skipEntityCreation = false)
{
if (!$data) {
$data = $this->getDI()->getRequest()->getPost();
}
if (isset($data['adapter']) && $data['adapter'] == '0') {
if (empty($data['cacheDir']) || !is_dir($data['cacheDir'])) {
$this->addError('Files location isn\'t correct!');
return false;
}
}
return parent::isValid($data, $skipEntityCreation);
}
/**
* Set form conditions.
*
* @return void
*/
protected function _setConditions()
{
$content = $this->getFieldSet(self::FIELDSET_CONTENT);
/**
* Files conditions.
*/
$content->setCondition('cacheDir', 'adapter', 0);
/**
* Memcached conditions.
*/
$content->setCondition('host', 'adapter', 1);
$content->setCondition('port', 'adapter', 1);
$content->setCondition('persistent', 'adapter', 1);
/**
* Mongo conditions.
*/
$content->setCondition('server', 'adapter', 3);
$content->setCondition('db', 'adapter', 3);
$content->setCondition('collection', 'adapter', 3);
}
}
Structure¶Root form class is abstract. So you can’t create it directly. Also it has abstract methods, that identifies form rendering feature. That’s why there is some simple form structure: AbstractForm
|
CoreForm EntityForm (trait)
|
|------- FileForm
|
|------- TextForm
Core form implements all necessary methods: <?php
namespace Core\Form;
use Engine\Form\AbstractForm;
/**
* Main core form.
*
* @category PhalconEye
* @package Core\Form
* @author Ivan Vorontsov <ivan.vorontsov@phalconeye.com>
* @copyright 2013-2014 PhalconEye Team
* @license New BSD License
* @link http://phalconeye.com/
*/
class CoreForm extends AbstractForm
{
const
/**
* Default layout path.
*/
LAYOUT_DEFAULT_PATH = 'partials/form/default';
use EntityForm;
/**
* Get layout view path.
*
* @return string
*/
public function getLayoutView()
{
return $this->_resolveView(self::LAYOUT_DEFAULT_PATH);
}
/**
* Get element view path.
*
* @return string
*/
public function getElementView()
{
return $this->getLayoutView() . '/element';
}
/**
* Get errors view path.
*
* @return string
*/
public function getErrorsView()
{
return $this->getLayoutView() . '/errors';
}
/**
* Get notices view path.
*
* @return string
*/
public function getNoticesView()
{
return $this->getLayoutView() . '/notices';
}
/**
* Get fieldset view path.
*
* @return string
*/
public function getFieldSetView()
{
return $this->getLayoutView() . '/fieldSet';
}
/**
* Resolve view.
*
* @param string $view View path.
* @param string $module Module name (capitalized).
*
* @return string
*/
protected function _resolveView($view, $module = 'Core')
{
return '../../' . $module . '/View/' . $view;
}
}
Text and file form extended from it and used for text rendering and file uploading features respectively. Entity trait used for forms that must be created according to some entity. Read more about each form type below. Elements¶Elements are objects and form/fieldset is a container for these objects. So you can add element to form by creating it and adding: <?php
// Create element.
$el = new Text("someName", [/*options*/], [/*attributes*/]);
// Add element with order 1001.
$this->add($el, 1001);
But this is a bit hard. So, there are exists some methods for element creation:
Default options of elements (not all allowed, and this is not a complete list, options can be added manually by element):
Non-default options:
List of all elements, their options and attributes:
Fieldsets¶Fieldset is a logical and/or visible separation. By default there are two fieldsets: content and footer. Content is for editable elements and footer is for buttons: <?php
class Create extends CoreForm
{
/**
* Initialize form.
*
* @return void
*/
public function initialize()
{
// Add elements to default content field set (field set key is 'form_content').
$this->addContentFieldSet()
->addText('name', 'Name', 'Name must be in lowercase and contains only letters.')
->addSelect('type', 'Package type', null, Manager::$allowedTypes)
->addText('title');
// Add buttons to footer (field set key is 'form_footer').
$this->addFooterFieldSet()
->addButton('create')
->addButtonLink('cancel', 'Cancel', ['for' => 'admin-packages']);
}
}
You can add your fieldsets or access them: <?php
// Get content field set.
$contentFieldSet = $this->getFieldSet(self::FIELDSET_CONTENT); // self::FIELDSET_CONTENT = 'form_content'
// Add new field set.
$fieldSet = new FieldSet('fieldSetName', 'Some legend, if needed', ['class' => 'css-class'], [/*... array of elements...*/]);
// Add elements.
// Elements adding methods are the same as for form class.
$fieldset->add<elementName>(...);
// Set flag for rendering feature, this will remove html div separation between elements, by default used for buttons at footer.
$fieldSet->combineElements(true);
// Adds css attribute to all elements inside fieldset with key: id="fieldSetName_elementName".
$fieldSet->enableNamedElements(true);
// Changes all elements css name attribute according to fieldset name: name="fieldSetName[elementName]".
$fieldSet->enableDataElements(true);
// Addd fieldset to form with order number 1001.
$this->addFieldSet($fieldSet, 1001);
Conditions¶Conditions allows to set relation between fields. For example we have 3 fields: select, text and text. Select and text must be visible always, but third text field must be visible only when select field has some specific value. Conditions allows you to setup such relation: <?php
// Parameters:
// 1) Field that will be checked on value change. Our "select".
// 2) Our "third text field" that will be shown/hidden.
// 3) Value that must be in select to satisfy this condition and show "third text field".
// 4) Comparison operator, you can find constants in Engine\Form\ConditionResolver. Allowed: ==, !=, >, <, >=, <=.
// This operator defines how value of fieldA must be compared to value that you entered in third parameter.
// 5) Summary operator. That operators also defined in Engine\Form\ConditionResolver. Allowed: 'and', 'or'.
// In case when "third text field" also related to "second text field" you can add new condition on that field,
// And in that case you will have two conditions, that's why you need to setup result operator - logical AND or OR.
// "third text field" will be shown/hidden state depends on result of conditions and their summary result.
$content->setCondition('fieldA', 'fieldB', 1, '==', 'and');
// Examples.
// Preconditions:
// select with values (1,2,3) - field1.
// text field - field2.
// text field - field3.
// Condition: field3 visible only when field1 has value '2' and field2 has value greater than '15'.
$content->setCondition('field1', 'field3', 2); // Comparison by default is '==' and result operator is 'and'.
$content->setCondition('field2', 'field3', 15, ConditionResolver::COND_CMP_GREATER);
// Condition: field3 visible when field1 has value '3' or field2 has lower or equivalent to '0'.
$content->setCondition('field1', 'field3', 3, ConditionResolver::COND_CMP_EQUAL, ConditionResolver::COND_OP_OR);
$content->setCondition('field2', 'field3', 0, ConditionResolver::COND_CMP_LESS_OR_EQUAL, ConditionResolver::COND_OP_OR);
// Condition: fieldSet 'footer' visible only when field1 has value '3'.
$this->setFieldSetCondition(self::FIELDSET_FOOTER, 'field1', 3);
This conditions allows to show/hide fields (all logic based on js, already implemented). Also it’s enables/disables validation for this fields and of course getValues method will return data without fields values if condition wasn’t successful. Form view¶AbstractForm class has some abstract methods:
This methods can be overridden, you can change one part of form view to your own. It means that you can simply change form style without problems to other forms. Layout view example: {{ form.openTag() }}
<div>
{% if form.getTitle() or form.getDescription() %}
<div class="form_header">
<h3>{{ form.getTitle() }}</h3>
<p>{{ form.getDescription() }}</p>
</div>
{% endif %}
{{ partial(form.getErrorsView(), ['form': form]) }}
{{ partial(form.getNoticesView(), ['form': form]) }}
<div class="form_elements">
{% for element in form.getAll() %}
{{ partial(form.getElementView(), ['element': element]) }}
{% endfor %}
</div>
<div class="clear"></div>
{% if form.useToken() %}
<input type="hidden" name="{{ security.getTokenKey() }}" value="{{ security.getToken() }}">
{% endif %}
</div>
{{ form.closeTag() }}
Entities support¶For example you have blog and you want to create form that will create blogs. You can associate form with entity and after validation you will have a new blog model. To associate form with entity you must add it per initialization. In most cases form for creation can be extended for form that will edit this blogs. So this must be respected: <?php
public function __construct(AbstractModel $entity = null)
{
parent::__construct();
if (!$entity) {
$entity = new Blog();
}
$this->addEntity($entity);
}
Done! To get your complete blog entity, just get it after validation. <?php
$this->view->form = $form = new CreateForm();
if (!$this->request->isPost() || !$form->isValid()) {
return;
}
$blog = $form->getEntity();
Note that blog already saved to database. If you don’t want to save it automatically, run validation with skip flag: <?php
$this->view->form = $form = new CreateForm();
if (!$this->request->isPost() || !$form->isValid(null, true)) {
return;
}
$blog = $form->getEntity(); // This entity isn't saved yet.
$blog->generateSlug();
$blog->save();
You can add multiple entities: <?php
public function __construct(AbstractModel $entity1 = null, AbstractModel $entity2 = null)
{
parent::__construct();
if (!$entity1) {
$entity1 = new Blog();
}
if (!$entity2) {
$entity2 = new Tag();
}
$this->addEntity($entity1, 'blog');
$this->addEntity($entity2, 'tag');
}
// In controller:
$blog = $this->getEntity('blog');
$tag = $this->getEntity('tag');
Validation¶Validation is divided. Validation can be defined for form, fieldset, entity. But all this validation is checked independently. If you like entity validation you can use it. For form validation internal validation system can be used. Example: <?php
$formOrFieldSet->getValidation()
->add('language', new StringLength(['min' => 2, 'max' => 2]))
->add('locale', new StringLength(['min' => 5, 'max' => 5]));
->add('email', new Email())
->add(
'controller',
new Regex(
[
'pattern' => '/$|(.*)Controller->(.*)Action/',
'message' => 'Wrong controller name. Example: NameController->someAction'
]
)
);
Filter¶Filter allows to filter entered values. There are some available filters (constants in AbstractForm class):
About filter system read in phalcon documentation. <?php
$form->addFilter('lifetime', AbstractForm::FILTER_INT);
Text Form¶This form is same as CoreForm, but it has changed views. In normal form all elements renders as control, in text form all element doesn’t renders, form takes only their values. CoreForm element view: <div class="form_element">
{% if instanceof(element, 'Engine\Form\Element\File') and element.getOption('isImage') and element.getValue() != '/' %}
<div class="form_element_file_image">
<img alt="" src="{{ element.getValue() }}"/>
</div>
{% endif %}
{{ element.render() }}
</div>
TextForm element view: <div class="form_element">
{% if instanceof(element, 'Engine\Form\Element\File') and element.getOption('isImage') %}
<div class="form_element_file_image">
<img alt="" src="{{ element.getValue() }}"/>
</div>
{% endif %}
{{ element.getValue() }}
</div>
File Form¶File form extended from CoreForm and contains additional checks for files validation, image transformation, files management, etc. FileForm is marked as ‘multipart/form-data’ and has additional methods. How to use it: <?php
$form = new FileForm();
if (!$this->request->isPost() || !$form->isValid()) {
return;
}
// Get all files from request.
$files = $form->getFiles();
// Get file of specific field.
$file = $form->getFiles('name');
Set file validation: <?php
$form->getValidation()->add('file', new MimeType(['type' => 'application/json']));
Set image transformations on upload (performed after validation, if valid): <?php
$form->setImageTransformation(
'icon',
[
'adapter' => 'GD',
'resize' => [32, 32]
]
);
‘adapter’ parameter is name of adapter that will be used (GD or Imagick). Other parameters are methods that will be called from adapter and value is parameters for this method ($gd->resize(32,32);). Entity Form (Trait)¶Entity trait was designed as light and simple way of form creation according to model. It applied to CoreForm as trait and can be accessible through different form types, for example text: <?php
$user = User::findFirst($id);
$this->view->form = $form = TextForm::factory($user, [], [['password']]);
$form
->setTitle('User details')
->addFooterFieldSet()
->addButtonLink('back', 'Back', ['for' => 'admin-users']);
EntityForm trait has one method “factory”: <?php
/**
* Create form according to entity specifications.
*
* @param AbstractModel[] $entities Models.
* @param array $fieldTypes Field types.
* @param array $excludeFields Exclude fields from form.
*
* @return AbstractForm
*/
public static function factory($entities, array $fieldTypes = [], array $excludeFields = []) {}
Field types parameter allows to change some fields html control (by default <input type=”text”/>). Exclude parameter allows to filter unnecessary fields. Grid System¶Grid is table of entities with actions, sorting and filtering. ![]() Same as in forms here is AbstractGrid and extended CoreGrid (abstract, too): <?php
abstract class CoreGrid extends AbstractGrid
{
/**
* Get grid view name.
*
* @return string
*/
public function getLayoutView()
{
return $this->_resolveView('partials/grid/layout');
}
/**
* Get grid item view name.
*
* @return string
*/
public function getItemView()
{
return $this->_resolveView('partials/grid/item');
}
/**
* Get grid table body view name.
*
* @return string
*/
public function getTableBodyView()
{
return $this->_resolveView('partials/grid/body');
}
/**
* Resolve view.
*
* @param string $view View path.
* @param string $module Module name (capitalized).
*
* @return string
*/
protected function _resolveView($view, $module = 'Core')
{
return '../../' . $module . '/View/' . $view;
}
}
Usage in controller: <?php
public function indexAction()
{
$grid = new UserGrid($this->view);
if ($response = $grid->getResponse()) {
return $response;
}
}
// yep.. that's all )).
Source¶Grid has source. Source can be QueryBuilder or Array. You can implement you own SourceResolver to handle different data. Usual QueryBuilder: <?php
// Method getSource is required.
public function getSource()
{
$builder = new Builder();
$builder
->columns(['u.*', 'r.name'])
->addFrom('User\Model\User', 'u')
->leftJoin('User\Model\Role', 'u.role_id = r.id', 'r')
->orderBy('u.id DESC');
return $builder;
}
Array usage: <?php
public function getSourceResolver()
{
return new ArrayResolver($this);
}
public function getSource()
{
$data = [['row1_column1' => 1, 'row1_column2' => 2], ['row2_column1' => 3, 'row2_column2' => 4]];
return $data;
}
Columns¶Columns must be defined per required method _initColumns(). Columns definition contains:
Example: <?php
protected function _initColumns()
{
$this
// Add simple text column, this means, that in filtering will be available text field.
->addTextColumn(
'u.id', // field name in query
'ID', // Label
[
self::COLUMN_PARAM_TYPE => Column::BIND_PARAM_INT, // Bind parameter, need to escape SQL injections.
self::COLUMN_PARAM_OUTPUT_LOGIC => // Special output logic.
function (GridItem $item, $di) {
$url = $di->get('url')->get(
['for' => 'admin-users-view', 'id' => $item['u.id']]
);
return sprintf('<a href="%s">%s</a>', $url, $item['u.id']);
}
]
)
->addTextColumn('u.username', 'Username')
->addTextColumn('u.email', 'Email')
->addSelectColumn(
'r.name',
'Role',
['hasEmptyValue' => true, 'using' => ['name', 'name'], 'elementOptions' => Role::find()],
[
self::COLUMN_PARAM_USE_HAVING => false, // Don't use HAVING
self::COLUMN_PARAM_USE_LIKE => false, // And don't use LIKE, '==' operator will be used ('=' IN SQL).
self::COLUMN_PARAM_OUTPUT_LOGIC =>
function (GridItem $item) {
return $item['name'];
}
]
)
->addTextColumn('u.creation_date', 'Creation Date');
}
Actions¶Actions also can be defined: <?php
public function getItemActions(GridItem $item)
{
$actions = [
'Manage' => ['href' => ['for' => 'admin-languages-manage', 'id' => $item['id']]],
'Export' => [
'href' => ['for' => 'admin-languages-export', 'id' => $item['id']],
'attr' => ['data-widget' => 'modal']
],
'Wizard' => [
'href' => ['for' => 'admin-languages-wizard', 'id' => $item['id']],
'attr' => ['data-widget' => 'modal']
],
'|' => [],
'Edit' => ['href' => ['for' => 'admin-languages-edit', 'id' => $item['id']]],
'Delete' => [
'href' => [
'for' => 'admin-languages-delete', 'id' => $item['id']
],
'attr' => ['class' => 'grid-action-delete']
]
];
if (
$item->getObject()->language == Config::CONFIG_DEFAULT_LANGUAGE &&
$item->getObject()->locale == Config::CONFIG_DEFAULT_LOCALE
) {
unset($actions['|']);
unset($actions['Edit']);
unset($actions['Wizard']);
unset($actions['Delete']);
}
return $actions;
}
getItemActions(GridItem $item) must return array of actions with parameters. ‘href’ is required parameter, ‘attr’ is optional. Grid View¶Grid view divided on three parts: layout (main layout, starting from <table> tag), body (tbody tag), item (td tag with actions). Each view can be overridden in grid class. Layout example: <table id="{{ grid.getId() }}" class="table grid-table" data-widget="grid">
<thead>
<tr>
{% for name, column in grid.getColumns() %}
<th>
{% if column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_SORTABLE')] is defined and column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_SORTABLE')] %}
<a href="javascript:;" class="grid-sortable" data-sort="{{ name }}" data-direction="">
{{ column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_LABEL')] |i18n }}
</a>
{% else %}
{{ column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_LABEL')] |i18n }}
{% endif %}
</th>
{% endfor %}
{% if grid.hasActions() %}
<th class="actions">{{ 'Actions' |i18n }}</th>
{% endif %}
</tr>
{% if grid.hasFilterForm() %}
<tr class="grid-filter">
{% for column in grid.getColumns() %}
<th>
{% if column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_FILTER')] is defined and instanceof(column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_FILTER')], 'Engine\Form\AbstractElement') %}
{% set element = column[constant('\Engine\Grid\AbstractGrid::COLUMN_PARAM_FILTER')] %}
{{ element.setAttribute('autocomplete', 'off').render() }}
{% endif %}
<div class="clear-filter"></div>
</th>
{% endfor %}
<th class="actions">
<button class="btn btn-filter btn-primary">{{ 'Filter' |i18n }}</button>
<button class="btn btn-warning">{{ 'Reset' |i18n }}</button>
</th>
</tr>
{% endif %}
</thead>
{{ partial(grid.getTableBodyView(), ['grid': grid]) }}
</table>
Helpers¶Mainly helpers existing for view extension. When in view must be performed some huge logic - this part of work can be moved to helper. Helpers can be accessible in view in such way: {{ helper('setting', 'core').get('system_title', '') }} {# Get setting 'system_title', with default value ''. #}
First parameter is helper class name (in that case, this will be Setting.php. Second parameter is namespace of this helper, by default this is ‘engine’, in that example - ‘core’. It means, that this class is accessible at CoreHelperSetting. After that call helper system returns your an object of CoreHelperSetting, this object created only once and by other calls it taken from cache (by singleton logic). Cache in that case is DI, so you also can check if helper is loaded by accessing it in DI: <?php
$di->has('Core\Helper\Setting');
Helper class can be used in other places: <?php
// Using static method.
$settingsHelper = \Engine\Helper::getInstance('setting', 'core');
$systemTitle = $settingsHelper->get('system_title', '');
// Or directly from required class.
$settingsHelper = \Core\Helper\Setting::getInstance($this->getDI());
$systemTitle = $settingsHelper->get('system_title', '');
Helper creation¶To create helper you need extend it from Engine\Helper and write your methods: <?php
class NewHelper extends \Engine\Helper
{
public function someMethod1(){}
public function someMethod2(){}
}
Existing helpers¶Here is list of available helpers:
Languages And Translations¶Multilanguages are supported out of the box. By default translation works from English to other languages (texts written in English). Translation system works in two modes:
Each language object contains name (title), unique key (e.g.”en”), locale (e.g. en_US). It means that you can have two equals English but with different locales. Translation in controller: <?php
// Get it from DI and translate.
$this->di->get('i18n')->_('Actions');
// Or like that.
$this->di->getI18n()->_('Options :one: and :two', ['one' => 1, 'two' => 2]);
// Or:
$this->di->getI18n()->query('Options :one: and :two', ['one' => 1, 'two' => 2]);
Translation in view: {{ 'Login'|i18n }}
Different tools: {# Format number according to current locale. #}
{# Output: 100.0 #}
{{ helper('formatter').formatNumber(100, php('\NumberFormatter::DECIMAL')) }}
{# Format currency according to current locale. #}
{# Output: $100 #}
{{ helper('formatter').formatCurrency(100) }}
Current language and it’s locale stored in session, so to change language: <?php
$language = preg_replace("/[^A-Za-z0-9?!]/", '', $this->request->get('lang', 'string'));
if ($language && $languageObject = Language::findFirst("language = '" . $language . "'")) {
$this->di->get('session')->set('language', $languageObject->language);
$this->di->get('session')->set('locale', $languageObject->locale);
}
Access Control List¶ACL is based on Phalcon ACL. Acl Roles are stored in database. Each user can have only one role. In production mode Acl compiles from database and is cached by the system. There is only one default Acl key: Core\Api\Acl::ACL_ADMIN_AREA (‘AdminArea’). This key is used for admin panel access. By default there are three roles: Admin, User and Guest. All not authenticated requests assigned to Guest Role. Logged-in sessions are assigned to User. Admin is the most privileged Role. ACL Usage¶Acl class is part of Core module API and can be accessed via api container (core container) from DI. In controller: <?php
// Check if current user has access to perform given action on the resource.
$this->core->acl()->isAllowed($viewer->getRole()->name, $resource, $action) == Acl::ALLOW;
// Get allowed value on given resource for user.
$this->getDI()->get('core')->acl()->getAllowedValue($resource, $viewer->getRole(), $valueName);
In view: {# Check if user is allowed to view, and show something. #}
{% if helper('acl').isAllowed('\Core\Model\Page', 'show_views') %}
<div class="page_views">{{ 'View count:'|i18n }}{{ page.view_count }}</div>
{% endif %}
{# Check if user is allowed to view, and show something. #}
{{ helper('acl').getAllowed('\Core\Model\Page', 'page_footer') }}
Model ACL¶Let’s take the Blog system as an example. We can allow or disallow access for some roles to perform actions such as: “create”, “edit” and “delete”. Also we have two values:
We can also define required actions and their values in blog model via annotation @Acl: <?php
/**
* Blog model.
*
* @category PhalconEye
* @package Blog\Model
* @author Ivan Vorontsov <ivan.vorontsov@phalconeye.com>
* @copyright 2013-2014 PhalconEye Team
* @license New BSD License
* @link http://phalconeye.com/
*
* @Source("blogs")
* @Acl(actions={"create", "edit", "delete"}, options={"blog_footer", "blog_count"})
*/
class Blog extends AbstractModel
{
}
After defining required actions and values you can set their values in admin panel via Access Rights system. Note: In development mode PhalconEye will automatically pick up all changes made to models. Console¶Console allows to run some commands for remote purpose. To run console manager, you will need to move into website root directory and execute: php public/index.php command param1 param2
Usage¶To use command simple type command and/or sub commands if required: php public/index.php database update
Some commands have aliases: php public/index.php db update
To show all available commands you can type: php public/index.php
## or ##
php public/index.php help
Help can be used for command: ![]() Or sub command: ![]() Commands¶
Command Creation¶Command can be created in module. Special directory “Command” must be placed in module root. Command example: <?php
/**
* Assets command.
*
* @category PhalconEye
* @package Core\Commands
* @author Ivan Vorontsov <ivan.vorontsov@phalconeye.com>
* @copyright 2013-2014 PhalconEye Team
* @license New BSD License
* @link http://phalconeye.com/
*
* @CommandName(['assets'])
* @CommandDescription('Assets management.')
*/
class Assets extends AbstractCommand implements CommandInterface
{
/**
* Install assets from modules.
*
* @return void
*/
public function installAction()
{
$assetsManager = new Manager($this->getDI(), false);
$assetsManager->installAssets(PUBLIC_PATH . '/themes/' . Settings::getSetting('system_theme'));
print ConsoleUtil::success('Assets successfully installed.') . PHP_EOL;
}
}
Each command must be extended from AbstractCommand and implements CommandInterface. Commands metadata defined via class annotations: <?php
/**
* @CommandName(['commandname', 'commandalias'])
* @CommandDescription('Description of the command.')
*/
class SomeCommand extends AbstractCommand {}
/**
* Command can gave initialization method, that will be performed before any action.
*
* @CommandName(['commandname', 'commandalias'])
* @CommandDescription('Description of the command.')
*/
class SomeCommand extends AbstractCommand {
public function initialize() {}
}
/**
* To define sub command - add subcommandAction method. It will be automatically added as sub command.
*
* @CommandName(['commandname', 'commandalias'])
* @CommandDescription('Description of the command.')
*/
class SomeCommand extends AbstractCommand {
public function subcommandAction() {}
}
/**
* Parameters of sub command automatically takes as parameters of it.
* NOTE: action with parameters must be commented well, coz this will be a description of this commands!
*
* @CommandName(['commandname', 'commandalias'])
* @CommandDescription('Description of the command.')
*/
class SomeCommand extends AbstractCommand {
/**
* Test action with params.
*
* @param string|null $param1 Param1 - string. Example: "string".
* @param bool $param2 Param2 is flag.
*
* @return void
*/
public function testAction($param1 = null, $param2 = false) {}
// Help for this command will looks like this:
//Help for "commandname test":
// Test action with params.
//
//Available parameters:
// --param1=NULL (string|null) Param1 - string. Example: "string".
// --param2 Param2 is flag.
}
Assets¶Assets are public files which are bundled with Modules and the CMS itself. Before we start explaining how these are handled you should understand the difference between public and not-public files.
For example the Blog module has css, less, js files and images which can not be accessed directly because of their location on the web server (/app/modules/Blog). Since these files must be available publicly the Assets system was implemented. It will take the files, merge, minify and copy them over to the public folder, that’s it! You will never need to worry about doing all this on your own. There is a console command to install the Modules’ Assets and compile Theme’s less files: php public/index.php assets install
Files will be available under public directory, public/assets: public/assets
├── css // All css files from all modules.
│ ├── blog // Module name.
│ ├── constants.css // Constants from theme.
│ ├── core
│ │ ├── admin
│ │ │ └── main.css
│ │ ├── install.css
│ │ └── profiler.css
│ ├── theme.css // Main theme file.
│ └── user
├── img // All images from modules.
│ ├── blog
│ ├── core
│ │ ├── admin
│ │ │ └── content
│ │ │ ├── middle_bottom.png
│ │ │ ├── middle_left_bottom.png
│ │ │ ├── middle_left.png
│ │ │ ├── middle.png
│ │ │ ├── placeholder.png
│ │ │ ├── right_middle_bottom.png
│ │ │ ├── right_middle_left_bottom.png
│ │ │ ├── right_middle_left.png
│ │ │ ├── right_middle.png
│ │ │ ├── top_middle_left.png
│ │ │ ├── top_middle.png
│ │ │ ├── top_right_middle_left.png
│ │ │ └── top_right_middle.png
│ │ ├── grid
│ │ │ └── filter-clear.png
│ │ ├── loader
│ │ │ ├── black.gif
│ │ │ └── white.gif
│ │ ├── misc
│ │ │ ├── icon-chevron-down.png
│ │ │ └── icon-chevron-up.png
│ │ ├── pe_logo.png
│ │ ├── pe_logo_white.png
│ │ ├── profiler
│ │ │ ├── bug.png
│ │ │ ├── close.png
│ │ │ ├── files.png
│ │ │ ├── memory.png
│ │ │ ├── sql.png
│ │ │ └── time.png
│ │ └── stripe.png
│ └── user
├── javascript.js // Merged js files. Will be used in production mode.
├── js // All js files, not merged.
│ ├── blog
│ ├── core
│ │ ├── admin
│ │ │ ├── dashboard.js
│ │ │ ├── languages.js
│ │ │ └── menu.js
│ │ ├── core.js
│ │ ├── form
│ │ │ └── remote-file.js
│ │ ├── form.js
│ │ ├── i18n.js
│ │ ├── pretty-exceptions
│ │ │ ├── js
│ │ │ │ ├── jquery.scrollTo-min.js
│ │ │ │ └── pretty.js
│ │ │ ├── prettify
│ │ │ │ ├── lang-apollo.js
│ │ │ │ ├── lang-clj.js
│ │ │ │ ├── lang-css.js
│ │ │ │ ├── lang-go.js
│ │ │ │ ├── lang-hs.js
│ │ │ │ ├── lang-lisp.js
│ │ │ │ ├── lang-lua.js
│ │ │ │ ├── lang-ml.js
│ │ │ │ ├── lang-n.js
│ │ │ │ ├── lang-proto.js
│ │ │ │ ├── lang-scala.js
│ │ │ │ ├── lang-sql.js
│ │ │ │ ├── lang-tex.js
│ │ │ │ ├── lang-vb.js
│ │ │ │ ├── lang-vhdl.js
│ │ │ │ ├── lang-wiki.js
│ │ │ │ ├── lang-xq.js
│ │ │ │ ├── lang-yaml.js
│ │ │ │ ├── prettify.css
│ │ │ │ └── prettify.js
│ │ │ └── themes
│ │ │ ├── default.css
│ │ │ ├── minimalist.css
│ │ │ └── night.css
│ │ ├── profiler.js
│ │ └── widgets
│ │ ├── autocomplete.js
│ │ ├── ckeditor.js
│ │ ├── grid.js
│ │ └── modal.js
│ └── user
└── style.css // Merged css files. Used in production mode.
To install assets from the code: <?php
$assetsManager = new Manager($this->getDI(), false);
// Install assets, using theme directory.
$assetsManager->installAssets(PUBLIC_PATH . '/themes/' . Settings::getSetting('system_theme'));
// First parameter - refresh assets, this means that old will be removed, new - added.
// If first parameter is true - second is required (theme directory).
$assetsManager->clear(true, PUBLIC_PATH . '/themes/' . Settings::getSetting('system_theme'));
// Get assets collections for JS or CSS:
$assetsManager->getEmptyJsCollection();
$assetsManager->getEmptyCssCollection();
// Add inline scripts (css or js) to <head>.
$assetsManager->addInline('test', '<link rel="stylesheet" href="../../_static/css/docs.css" type="text/css"/>');
$assetsManager->removeInline('test');
You can get more details about the Assets from Phalcon documentation. Cache¶Cache is required to improve performance. PhalconEye has 4 types of cache:
In development mode all cache data will be handled with non-persistent Dummy layer which does not store any data. You can read more about cache system in Phalcon documentation. Other formats¶ |