Dear reader
Welcome and thank you for choosing this product.
This program took years of hard development work and many hours that numerous helping hands invested by providing notices, suggestions and tests to steadily improve the quality of the program.
All these hours have been lots of joy to me and all those who have been working on this projects.
We hope that the result of our work will be useful and give you the same pleasure that we had programing it. Please provide us with your experiences and comments. If you got suggestions on how to improve this work, I will appreciate your e-mail.
Thomas Meyer
Yana supports the development of applications by providing ready-to-use components. Examples: user management, authentication, database API, or templates. In addition it helps developers to write clean code by enforcing the use of a 3 layer architecture. (Compare: "Model-View-Controler").
More features are in the following list:
The following minimum conditions have to be met so the program can run properly:
The following configuration is recommended:
In this section you will find references to peculiarities of certain system configurations, as well as information on typical configuration errors and advice that can help you, if you encounter problems during the installation.
1 | index.php | index site |
2 | library.php | loads system libraries |
3 | cli.php | command-line program to run cron-jobs |
4 | cache/ | directory for temporary data and log files |
5 | common_files/smilies/ | new emot-icons may be placed here |
6 | config/ | configuration files |
7 | engine/ | template engine |
8 | languages/ | translators may add their language packs here |
9 | plugins/ | PHP programmers may put their extensions and applications here |
10 | skins/ | web designers may create and edit additional skins and layouts here |
11 | config/db/ | databases and configuration files |
12 | config/db/*/ | uploaded files (binary large objects) are stored in .blob/. Configuration files use the extension "config". All data resides in its own respective directories. |
13 | config/profiles/ | settings made in the administration menus |
14 | config/_drive.config | path setting for system files |
15 | config/dbconfig.php | parameters for your database connection |
16 | config/system.config | system configuration |
Note on licences:
The installation package contains software components by different authors.
"Yana Framework for PHP" by Thomas Meyer
This software is published under the GNU GPL. See the license here ( gpl.txt ).
"Manual to Yana Framework" by Thomas Meyer
This manual is published under a Creative Commons Attribution License 3.0. See the license here ( creativecommons.org/licenses/by/3.0 ).
"Smarty Template-Engine" by New Digital Group
This software is published under the GNU LGPL. See the license here( lgpl.txt ).
"PhpConcept Library - Zip Module" by Vincent Blavet
This software is published under the GNU LGPL. See the license here( lgpl.txt ).
"The DHTML Calendar" by Mihai Bazon
This software is published under the GNU LGPL. See the license here( lgpl.txt ).
"CKEditor" by Frederico Knabben
This software is published under the GNU LGPL. See the license here( lgpl.txt ).
"JQuery" by John Resig
This software is published under the MIT-Licence. See the licence here( mit.txt ).
The place of jurisdiction is the home town of the author.
You are using the software and the included documentation on your own risk. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
Every user is able to take an active part in the development of this software. A wishlist for future program features and a page to announce discovered bugs can be found at the forum of the project's web site.
In addition you are invited to contribute by writing translations, add your own plug-ins or skins. This manual provides help on completing the first steps. For further questions and discussions with other users you may use the internet forum at any time.
If you don't have the possibility to take part in the project directly, please consider donating to the project. Server and traffic are expensive, as well as literature and hardware - not the mention the effort put in the work on this software. The project's web site explains several ways how to make your donation.
If you would like to link to our web site from your home page, we would be pleased if you do so.
Author: Thomas Meyer
Internet: www.yanaframework.net
Mail:
Please remember to check the system requirements and notes on compatibility.
The Yana Framework has a tool, which can accomplish the installation for you.
To run the installation program do as follows:
When starting the installation program you should see the following page:
Click on "next" to continue
Read the license conditions carefully. In order to continue with the installation, you must accept the license conditions. If you do not want to use the program, you can abort the installation by clicking the button below.
To accomplish the installation click the button "start installation now". When you are finished, click "next".
On this side you specify a password. With this password you can enter the administration menu of the application. To do so, use the name "administrator". First please enter the password in the left field. Second please retype the password in the right field, just to ensure you didn't mistype it. Following this please click on "OK".
A message will be shown that indicates whether or not the operation has been successful. When you are finished, click "next".
In order to make sure that the installation was successful, you may want to run the self diagnosis of the program. To do so click on "OK". Consider the viewed operating instructions.
If you wish additional details to the be printed, mark "more details" and click "OK" again. If you are content with the installation, continue by clicking on "next".
After completion of the installation the program automatically deletes all temporary files. If you check the option "run the application now", the YANA Framework is started automatically.
To end the installation program click on "finish".
Please remember to check the system requirements and notes on compatibility.
In WS-FTP (for example) you can change access rights by right-clicking on the directory names and selecting the option "file attributes" from the context menu. Then check all shown options and click "OK".
In FireFTP you can change access rights by right-clicking on the directory names and selecting the option "Properties (incl. contents)" from the context menu. Then check all shown options and click "OK".
If you are using another FTP program, set the rights via the command CHMOD 777
. If you experience problems finding the "right button", search the documentation of your program for the keyword "CHMOD".
Open in your browser the file "index.php?action=index", which is stored in the program's main directory.
You will be asked to log on to the system with your user name and associated password. (see figure)
Figure: login - Screenshots: Firefox browser
Enter the text "administrator" as "Login".
Confirm your entry by clicking on "OK". Then you may access the administration panel. Please note that JavaScript should be activated in your browser, so that the next page can be displayed correctly. If JavaScript is disabled you may experience that some features are not available. Personally I recommend you use Firefox 1.5 or later to view this page.
The following section describes how to change the password for your login.
Tip: To avoid, that someone may guess your password, it should be a minimum of 8 characters in length and contain at least 2 special characters. For example: birthdays, names of persons or animals and terms used in standard dictionaries are easy to guess.
Write down your new password and keep it safe.
Note: if the access rights were not set, as described in the "installation" chapter, you will get an error message at this point. In that case read again, what you need to do and execute the instructions step by step. If required contact your operator.
Note: Please understand that not all the options and settings are explained in detail. The following text will therefore only comment on the most important settings. Where needed follow the references in the program.
If you now want to set up your own settings for your application, please select "page layout Setup" from the options table. On this page you may define the layout of the program. E.g. font size, font color or date format. Colors must be specified as hexadecimal values. If you do not know how to do this, click on the square, colorful button next to the field. This will open a pop-up window, which let's you choose a color. Please also read the help text on this page for more information.
Figure: layout Options
The installation is done in several steps:
The most important steps to configure the application:
The program has a profile administration, which allows, several sets of settings (profiles) to be stored and operated at the same time. First of all, you should choose the default profile and adapt it to your needs. Choose one after the other the "setup" entries from the options menu to edit the settings, which are related to the functions of the program. The administration menu offers a number of different options.
Tip for web communities: For example, if you have several guest books and want one of your books to use very special settings, which differ from the default values, use the field "new profile" in order to create an own profile for this guest book. Enter the ID of the guest book and click "GO". The options are the same as for the basic settings. Once the settings are saved, the name of the new profile appears in the selection menu of available profiles. This way you can access all saved profiles at any time and edit them.
This program can have any number of users simultaneously using separate instances of the application. It is a multiuser system. For example, if you use the guestbook application, you could assign each user to a separate guest book.
To be able to distinguish between all these guest books, each has a unique ID which clearly identifies this book (like giving it a "name"). The id is also available in the URL of the browser as the optional parameter "id".
You can create multiple profiles. A profile is a set of properties that apply to the guest book. There is always 1 profile for exactly 1 guestbook.
If a guestbook has no own profile, then default values are used. The default values are stored in a separate profile with the name " default settings ".
At the administration menu of your application, you will find a list of all profiles, which have been created until now. You can use this option to edit your different profiles, if more than 1 profile exists. A profile is only for the guest book with the appropriate ID.
You may create additional profiles using the option " create new profile ". To create new profiles, you need to have administrator privileges.
The user administration for communities is disabled by default. To enable this option (requires administrator priviliges), select the option "on" next to "user administration, configuration" from the menu "Plugins" in the administration menu. ("on" is the left of the two switches) then click "Save Changes". In the left column of the main menu a new menu labeled "user administration" will automatically appear, which allows you to create new users, set passwords and grant or revoke access rights.
Please check first whether your connection parameters are correct and the database server is available. You may also want to check the compatibility notes, as these contain guides and solutions to known problems with some DBMS.
If the connection parameters are correct and there is no problem with the database driver or a version conflict, you may circumvent the problem by trying a manual installation. To do so, read the chapter trouble shooting.
Administrators do as follows to set a new password:
Figure: menu "user overview"
Figure: list of all users
Figure: generate new password
If you have forgotten your own password, please contact another administrator. If you ARE the ONLY administrator, then no new password can be generated. However it is also possible to reset the old password manually.
If the installation works with text files, please connect via FTP to your account, and download the file "config/db/user/user.sml". Edit this file and replace the password with the text "UNINITIALIZED". Upload the file again, and set the access rights via CHMOD to 777.
If the installation works with a database, open a connection to the database by using an appropriate program (for example PHPmyAdmin for MySQL) and edit the table "user" and set password of user "Administrator" to "UNINITIALIZED".
Alternatively, you can simply execute the following SQL statement.
UPDATE `user` SET user_pwd = 'UNINITIALIZED' WHERE user_id = 'ADMINISTRATOR';
(please adapt the quoting for the corresponding DBMS)
Remember to set a new password afterwards.
Note: This script never stores the password as real text Instead it is encrypted after input. This is an one-way encryption which cannot be reversed. For this reason, it is not possible to retrieve a forgotten password on demand - instead it is always required to create a new one.
Choose the smilies you want to insert. Please note that these graphics are to be stored in GIF format.
Copy the files to the directory " common_files/smilies
" of the framework.
If you want to use smilies from a directory other than the prescribed, then proceed as follows.
If you have multiple profiles, you may need to copy the settings for all other profiles as well.
Refer to the section " Installation for Beginners ". Since version 2.2 the program has a self-diagnosis mode. If you have finished the installation as described in the manual, you can use it to verify that all files are installed correctly.
For the self-diagnosis to start, open the URL "index.php?action=test" in your browser program. The program will then begin to create a diagnostic protocol. The self-diagnosis tests important directories and files and reports errors. These are shown as red lines in the protocol. If an error is found, read the error message and follow the instructions.
The self-diagnosis finds some of the most typical installation errors, but may not discover all kinds of possible problems. If you have installed the program according to instructions and still encounter problems during operation, send an email to. Don't forget to add the URL and a description of your problem. This offer is available to registered users free of charge.
The database setup offers an option labeled "prefix for database" (see figure ). This option is EXPERIMENTAL and serves the only purpose, of running two installations of the YANA Framework on the same server and the same database without having them affect each other. In any situation where this is not needed it is strongly recommend that this option is NOT used, due to it's experimental status.
Figure: experimental option to set a prefix on a database connection
A typical application for this option would be if someone is switching from an older to a new version, and therefore wants to temporarily run tow installations for testing purposes, to test the new version in a real environment before making a permanent change.
If you use a prefix, this also means that the SQL installation files in the directory "config/db/.install" need to be corrected by hand, according to the new table names including the chosen prefix. Only then the database may be installed as usual. The is currently no automatic process available to apply these changes. The PHP source code, however, does not need to be changed.
If you use your own plug-ins with handwritten SQL statements, you must of course also change the source code there to use the reflect names of the tables. Should you, however, use the database API of the framework, and have your SQL statements generated automatically, then editing these files is not necessary. The framework will correct your SQL at runtime automatically.
You should schedule 5 to 15 minutes for the conversion of the SQL installation files.
If this option is used accidentally and you experience problems accessing your data, then you can may reset this option manually. To do this edit the file "config/dbconfig.php" and change the entry at line 9 to: define('YANA_DATABASE_PREFIX',"");
Note: in some versions of the application, this feature is intentionally disabled.
If you don't want to use the site map as the front page for the application, it is possible to replace it with a different page - for example, the start page of a plug-in of your choice.
To do this open the file "config/system.config" in any text editor of your choice and change the entry "DEFAULT.HOMEPAGE" as you see fit (see figure).
Figure: Changing the front page (default homepage)
The following is a list of suggestions of possible settings:
It is not allowed to insert HTML codes via a publicly available form. The risk that a person abuses this offer to insert unwanted content or manipulate the website in an unacceptable way would be too large Among other things, a malicious attacker could, for example insert scripts, which, for example, might be used to commence a "phishing attack" to view visitors a different site content in order to steal passwords of users.
Basically to ban HTML completely is also not useful, because some elements are totally harmless and the readability of text may benefit from their use. This requires a compromise.
This program works, as just described, as well as most other programs on the WWW with a "Whitelist". That is, some harmless tags are allowed. All other tags are not allowed and will be automatically removed.
For example: [b]this is bold text[/b]
This code is easily controllable and may be converted to clean HTML later: <b>this is bold text</b>
An abuse is no longer possible..
As a side effect, these simple tags are much easier to understand than pure HTML. This makes it easier for beginners to write new entries.
Icon | Tags | Description | Example | Output |
---|---|---|---|---|
![]() |
[b] | bold text format | [b]bold text[/b] | bold text |
![]() |
[i] | italic text format | [i]italic text[/i] | italic text |
![]() |
[u] | underline text | [u]underline text[/u] | underline text |
![]() |
[code] | insert source code (code is not executed, but formatted as text) |
[code]Text[/code] | Text
|
![]() |
[php] | insert PHP code (code is not executed, but formatted as text) |
[php]print 'Hello World';[/php] |
<?php print 'Hello World'; ?> |
![]() |
[color] | set a text color | [color=red]Text[/color] | Text |
![]() |
[mark] | set background color | [mark=yellow]Text[/mark] | Text |
![]() |
[url] | inset hyperlink | [url]www.yanaframework.net[/url] | www.yanaframework.net |
![]() |
[mail] | insert mail address | [mail]mail@domain.tld[/mail] | mail@domain.tld |
![]() |
[img] | insert graphic * | [img]domain.tld/img/image.gif[/img] | ![]() |
![]() |
[emp] | emphasize text | [emp]Text[/emp] | Text |
![]() |
[h] | insert header | [h]header[/h] |
header
|
![]() |
[small] | reduce font size | [small]small text[/small] | small text |
![]() |
[big] | increase font size | [big]big text[/big] | big text |
![]() |
[c] | insert comment (e.g. for writing signatures) |
[c]comment[/c] |
comment
|
![]() |
[hide] | hide a text, till the users moves the mouse over it | [hide]this is a secret[/hide] |
this is a secret
|
![]() |
[wbr] | allow line break within a word | ship[wbr]yard-[wbr]employee | shipyard- employee |
![]() |
[br] | force line break | ship-[br]yard-[br]employee | ship- yard- employee |
* YANA prevents attacks on your site through cross-site scripting (XSS), because it only the inclusion of graphics from the local server within the directory of the framework is permitted. The file must also have the file extensions "jpeg", "jpg, png", or "gif". Special characters are not allowed, which will prevent an attacker to conceal the true URL of a file by the skillful use of escape sequences, to try to sneak by the protection of the framework.
The tag [img] is useful for community sites, where users can upload their own graphics files on the server.
Warning: Incorrect settings may by chance prevent the program from running properly. Therefore never perform such changes in a productive environment. Instead check your settings first in a test environment.
It is possible to set up individual tags for each website profile. Proceed as follows.
<PROFILE>
...
<EMBTAG>
<tagname>
<TITLE>value of attribute "title"</TITLE>
<TEXT>label</TEXT>
<IMAGE>image of the button, that inserts the tag/IMAGE>
<1>optional regular expression to transform to HTML</1>
(default value = /\[$tagname\](.*)(?:\[\/$tagname\]|$)/Us)
<2>optional replacement string to convert to HTML</2>
(default = <span class="embtag_tag_{tagname}">$1</span>)
</tagname>
</EMBTAG>
</PROFILE>
<PROFILE>
...
<EMBTAG>
<bold>
<TITLE>insert bold text</TITLE>
<TEXT>bold text</TEXT>
<IMAGE>%SKINDIR%/default/styles/tags/b.gif</IMAGE>
<1>/\[bold\](.*)\[\/bold\]/Us</1>
<2><b>$1</b></2>
</bold>
</EMBTAG>
</PROFILE>
Note: If you remove a tag, it is no longer viewed as HTML as well. This includes such data, that has had this tag earlier. Also be sure not to remove or replace the original tags.
Take your question to the internet forum at yanaframework.net/forum. This offer is free of charge. Please understand, that there is no guarantee that unsolicited e-mail is handled. Please respect, that requests by known donators, who engage in the further development of this project, may be answered faster than normal mail.
To open the administration panel, do as follows:
Click the link "administration panel". I can be found at the bottom of every page.
Figure: position of link "administration panel"
You will be asked to log on to the system with your user name and associated password.
Figure: login - Screenshots: Firefox browser
Enter the text "administrator" as "Login".
If you have forgotten your password continue reading at the section "frequently asked questions".
Confirm your entry by clicking on "OK". Then you may access the administration panel. Please note that JavaScript should be activated in your browser, so that the next page can be displayed correctly. If JavaScript is disabled you may experience that some features are not available. Personally I recommend you use Firefox 1.5 or later to view this page.
The administration panel comes in two flavors: basic and expert mode. Some options are only visible in expert mode. To switch from basic to expert, click the link "switch to expert mode".
Figure: Position of link to switch between configuration modes
Figure: administration panel's options in basic mode
Note: Installed plug-ins may only be activated or deactivated in expert mode.
Figure: administration panel's options in expert mode
A "Skin" is a package of template pages and graphics. This package changes the appearance of the user interface, the "look & feel" of your application.
Attention! Packages may not include executable files or PHP files. Take care of this before you proceed with the installation.
To install an archive you got from the internet, do as follows:
Figure: Choosing a skin (administrator's menu / expert-mode)
The changes will take effect, the next time you start the program. If the settings do not apply as expected, flush the server's cache by clicking the button in the administrator's menu and try again.
Plug-ins extend your application to include new functions.
To install an archive you got from the internet, do as follows:
To the deactivate the plug-in at a later stage, repeat the procedure and remove the check mark next to the name of the plug-in.
The changes will take effect, the next time you start the program.
Language packs contain translated text messages of the application for various languages. For example German or English.
After installation of a new plug-in you might need to update an installed language pack. For example, if new text messages were added with the release.
Attention! Packages may not include executable files or PHP files. Take care of this before you proceed with the installation.
To install an archive you got from the internet, do as follows:
Figure: install new language file
The changes will take effect, the next time you start the program.
This plug-in enables you to add a guestbook to your web site. Using this software, your visitors may leave messages for you. These messages are visible to you and other visitors. The software automatically informs you on new entries by sending you an e-mail. In addition the latest messages are also available as RSS-feed.
Guestbook entries may be stored optionally as text files or in a database.
To be able to run this script you need webspace with support for PHP and FTP access. In addition a FTP client is needed to upload files. To use a database to save entries you need to have the PEAR-DB library installed.
Step-by-step guide:
Figure: Choosing the profile "Basic settings"
These settings automatically apply to all profiles, unless a profile overwrites these basic settings. In addition they apply to all newly created profiles. Caution: Some plug-ins may have options, either which are only visible in the basic settings, or which are visible everywhere EXCEPT in the basic settings.
Note: Basic settings are meant to be "Default values", that automatically apply to all books, unless overwritten.
In the administrator's menu click "Guestbook Setup". In the section "E. mail notifications" you can choose whether you are to be informed on new entries automatically by e-mail. This option is disabled by default. To activate it, click on the button with the text "activate" and enter your e-mail address in the box "send mail to". (see figure)
In order to save your changes, click on "Save changes", or click on "Abort" to return to the administration menu without changes.
The program usually sets up new guests books when needed. When doing this, basic settings are used, which apply to all books equally (see above) If you want a guest book to use very special profile of settings, which differ from the basic settings of all the other guestbooks, then you can use this option.
Link to your new guestbook like this:
index.php?id=FOO&action=guestbook_read_read
Where instead of FOO you enter your guestbook's name.
default
". (see figure)
Figure: Using data from another profile
Do the same for any other layouts you want to create.
Link to a layout as you would do with a real guest book:
index.php?id=FOO&action=guestbook_read_read
Where instead of FOO you enter your layout's name.
To protect you guestbook from being flooded with unwanted advertisement there are several security options included. Via the menu "Guestbook Setup" you may restrict the number of entries per person to a number of your choice.
Figure: Spam protection
Additional security settings can be made by using the plug-in "Anti-Spam". These can be edited by using the menu option "Anti-Spam Setup".
To see the full list, insert new fields, or delete old fields, open the appropriate structure file "config/db/guestbook.config".
Column | Type | Mandatory | Default value | Description |
---|---|---|---|---|
GUESTBOOK_ID | integer | yes | <auto increment> | Primary key |
PROFILE_ID | string | no | default | Foreign key to the appropriate profile. |
GUESTBOOK_IP | string | no | <remote address> | IP of the author of the current entry |
GUESTBOOK_NAME | string | yes | - | Name of the author |
GUESTBOOK_MESSAGE | string | yes | - | message text |
GUESTBOOK_MAIL | string | no | null | Mail address of the author of an entry. |
GUESTBOOK_DATE | integer | yes | <current timestamp> | Date of creation. |
GUESTBOOK_COMMENT | string | no | null | Commentary by the webmaster (if any). |
This plug-in provides protection from unwanted advertisements This protection automatically applies to all installed plug-ins. For configuration an additional "setup"-plug-in is required. This adds the option "Anti-Spam Setup" to the administrator's menu. This menu enables you to set up the configuration for this plug-in.
The edit the configuration, do as follows.
Figure: Anti-Spam Setup
Display of the CAPTCHA in an application Here: guestbook plug-in
This plug-in enables you to edit the connection settings for your database. Furthermore it allows you to install table, transfer required data and create backups.
Figure: database connection settings
Set the option "use database" to the value "yes", to use the database connection with the given values. The installation of the database is automatically implemented. Only in the event of an error, you need to manually intervene.
Should any of these steps result in an error, the connection is not activated and an error message is shown. A detailed description of the error can be found in the error log. Please note that logging must have been activated at this point (see chapter "logging and error messages").
In case of an error, you may also complete the steps manually, which usually would be carried out automatically during activation of the database connection. To do so, proceed as follows.
Please also note the compatibility notes!
Note: This step is executed automatically, when a database connection is activated. The form shown here is meant to be used for manual intervention in case of an error.
Figure: form to install tables
This step will try to create the tables belonging to the chosen databases on the database server. Therefore it will try to import sql files, appropriate to chosen type of database. These requires prior proper set up of a connection to the database. You should however not yet activate this connection before you have installed the required tables.
In some uncommon cases this process may fail. For example, if: connecting to the database server fails, there is no SQL file available for the chosen DBMS, the generated SQL contains errors, another table with the same name already exists, the user does not have the appropriate rights to create the tables. In those cases the installation needs to be done by hand.
The required SQL files to create the tables can be found in chapter "Advanced User's Guide" / "Configuration".
Note: This step is executed automatically, when a database connection is activated. The form shown here is meant to be used for manual intervention in case of an error.
Figure: form to copy table contents to your database
This synchronizes the entries in your text files with those in your database. The process does NOT delete or update any existing entries, but only creates new ones. When operating on very big amounts of data you may experience a timeout before the process is completed. If this happens, redo this procedure multiple times until all data has been transfered.
Note: This option requires an active database connection.
Figure: form to create a backup of a database
Use this option to create a backup copy of your database.
This scripts lets your visitors search the content of your web site. You don't need programming knowledge to be able to use this software.
The Sun Java VM is required for installation of this package. This is a free product that you can get at http://java.sun.com.
tools for creation of search indexes | |
---|---|
tools/index.jar |
containts program code |
tools/start.bat |
start file |
tools/suchIndexErstellen.bat |
Command line interface (German) |
tools/searchIndex.bat |
Command line interface (English) |
misc. files | |
plugins/search |
directory for program files of the search engine |
plugins/search_admin
|
administrative application |
skins/default/search
|
template files |
Tip: If you prefer to work offline on your local machine, create the search index first.. To do this use the file "
start.bat
" in directory " tools/
". For further details on the usage of this tools see the next section. The search index itself will be automatically created and saved to the files keywords.dat
and documents.dat
. Once these files are created, your search engine is ready for use.
Install search engine:
Tip: The files in directory "tools/" do not need to be uploaded to the internet.
For better performance while searching the search engine uses an index of all searchable documents. This directory is called a "search index". Before you are able to use the search engine, this index needs to be created. For the search index you may either: 1) create it offline on your local machine and upload it (either by FTP or via the administration menu of the program) to your server, or 2) create it online via the administrator's menu of the program. Both alternatives are available, so just choose the one that serves you best. The upcoming chapter will first explain how to create the search index offline on your own computer. Afterwards a second chapter will explain how to create the search index online on a web server.
You don't have to enter all pages of your web site by hand. The package contains a small Java tool, that can do the work for you.
It is not relevant whether you are running this software under Winows, Linux, Unix or Mac. OS. However what you do need is a working runtime environment. Get the most up to date version for your system for free at Java.Sun.com. You need at least Java 2 version 1.4.2 or later with the Swing- and Beans-libraries installed. For Linux, Java is already included in many distributions. For Windows this is not necessarily the case.
To find out whether Java is already installed on your system or not, just search for the file "java.exe". On Windows choose "Start" > "Search" > "Files and Folders". If the file is not found, Java still needs to be installed.
After installation, the file needs to be added to the system's path variable. You may check this in Windows by opening the "DOS command line" from the menu "Start" and typing "java". A list of options should then be displayed to you. If not, the file still needs to be added to the system's path.
In Windows XP you do this via the system's setting calling "System" > "Extra" > "Environment variables" > "System variables". Restart your computer afterwards and try again, by entering "java" at the command line. If you still do not succeed at this point, you have no other choice then to install Java again.
For previous versions of Windows please edit the file "autoexec.bat". Add the following entry (don't forget to make yourself a copy of this file first):
SET PATH="JAVA-folder\bin\java.exe;%PATH%"
You can get the installed version of Java under Windows by typing "java -version" at the DOS command line.
Just start the file start.bat by double-clicking it. The program has a graphical interface with colorful icons which should be self-descriptive. This program declares all necessary input and will create the search index on a click.
Tip: you do NOT have to use the command line, if you don't want to. There is a graphical user interface, which you can use by running the file "start.bat". If this does not work, or you don't like it, you still can run the program from the command line.
You can use the DOS command line, or utilize the file "suchIndexErstellen.bat" to run it.
By default this file is set to search the current directory plus all subdirectories. If you want to change this, edit the file or run the program manually from the command line. Otherwise it is enough to simply execute the file by double-clicking it.
Using the command line interface:
java -classpath index.jar suchindexErstellen FOLDER FOLLOW META_TAGS
FOLDER: is the directory that is to be searched E.g. the current directory is .\ the parent directory is ..\ if the directory would be "home", use home\ aso.
Please use relative path names only (e.g. not C:\\...) otherwise files would be linked to your local hard disk.
FOLLOW: lets you choose whether subdirectories should be searched as well, or not. Choose true if you want this to be done or false if not.
META_TAGS: here you may choose, whether keywords included in meta tags should be included in the search results or not. Select true if you want this, or false if not.
Examples:
1) If your front page is at "C:\Homepage" and your search engine is installed at "C:\Homepage\search", the Java files need to be copied to "C:\Homepage\search". Here state the following call (at the command line):
java -classpath
index.jar suchindexErstellen ..\ true true
2) Given your web pages are stored in "C:\Homepage", again your search engine is stored at "C:\Homepage\search", but you just want to search the directory "C:\Homepage\test" without subdirectories. Then enter the following:
java -classpath
index.jar suchindexErstellen ..\test\ false true
3) Given your web is (again) stored in "C:\Homepage", but this time your search engine is in the same directory and you just want to search the directory "C:\Homepage\test" and it's subdirectories, then the call would be:
java -classpath
index.jar suchindexErstellen .\test\ true true
Batch file:
Open the file searchIndex.bat
in a text editor of your choice (e.g. Notepad). Here you will find the same call as stated above, including the description. Change the settings as you see fit, save and start the file.
As an alternative to creating the search engine offline at your local computer, you may also have it created for you online on your web server. This way you don't have to upload the index files to the web server. On the other hand, all files, that are to be searched, need to be available on the same web server at the same time. In addition you can't check the files by hand, if you want to, before putting them online.
The database contains all words found in a document, "key words" and the title of the page.
The search is restricted to HTML documents (*.htm, *.html, *.xml, *.shtml). Beside the found words, the page's URL and first 80 characters as a description are stored too.
All directories whose names begin with an underscore: "_" are ignored. The reason is that some web site making programs (e.g. Frontpage) use this to mark system directory that, of course, are not to be searched.
Create and upload a new search index using either " searchIndex.bat
" or " start.bat
". Both files " keywords.dat
" and " documents.dat
" need to be replaced. Also flush the cache of the search engine (if any) stored in directory " cache/
" by deleting the file(s) " *.cache
".
This may occur while old data is still in the cache of the search engine. To do this open directory " cache/
", delete all files with the extension " *.cache
" and try again.
Displaying the hit list requires JavaScript. This makes the search engine extremly fast and relieves the server load, since no data has to be requested from the server while going through the pages of results. However: to be able to do this JavaScript has to be activated in your web browser. If the page stays empty, check if JavaScript is deactivated or blocked by your firewall software.
In directory "tools" there are 3 files "start.bat", "suchIndexErstellen.bat" and "searchIndex.bat". They all do the same thing. Just that "start.bat" offers a neat graphical interface, which asks all necessary data. The other two start the program directly at the command line. Here you need to adjust the arguments by hand. Here "searchIndex.bat" will show an explanation of the arguments in English, while "suchIndexErstellen.bat" will show the same text in German. Choose the solution, which suits you best.
Probably you don't have created the search index yet, or the files are stored in the wrong directory. To find out how to create a search index see the section "Creating the search index".
Probably you have forgotten to copy some important files to the internet. Please check if all files are present.
This may occur if insufficient access rights are set. Use your FTP program to check whether the following folders: " cache/
" and " config/
", plus all subdirectories and files have sufficient access rights set (should be: readable AND writable). For UNIX you can set this by using the command CHMOD 777. A detailed explanation of access restrictions can be found in the installation guide for beginners.
Please check if you have at least version 1.4.2 of the Java Virtual Machine installed. To check this enter "java -version" at the command line. If you have an older version, download a more current version of the Java Virtual Machine for free from the internet at java.sun.com. Or try to use the file "searchIndex.bat" to run the program from the command line. While this is not as comfortable, it should even work with version 1.3 of the Java Virtual Machine.
It is strongly recommended, that you install the SDK only on web servers, which are not accessible by the public. For example, on a local workstation, which is not accessible over the network. The SDK is used exclusively for the purpose of developing new applications for the Framework. To make best use of it, it is necessary that the user can work locally at the respective computers and has read and write permissions for the directories and files of the framework, which has the SDK installed. Or at least he should be in a position to have a remote connection, that allows to work with the same permissions like a local user.
Improper use could delete or overwrite existing applications of the Framework. It is therefore strongly recommended that backup copies of stored files are created regularly.
In order to install the SDK proceed as follows:
If you wish an introduction on how to use the SDK, see chapter "Creating plug-ins and applications". For a practical example, see the introductory tutorial, at chapter 3 "Creating a new plug-in".
general information
Some information on you, the author of the software:
If you want to set up other actions, you can define their interface here. To do so you may use one of the templates, which the SDK suggests. A manual creation is also possible. The interfaces can also be manually changed.
For some actions their are standard templates, which can be used. Have a look at the select box. If an option is selected, a list of all generated actions is shown for comparison. You can review the entire interface by selecting the tab "edit interface manually".
An interface contains the following information:
This option may be used to control the results, or as an alternative input interface for experienced users. You see all defined actions and can make changes.
Beware! Errors may cause, that the plug-in cannot be generated correctly.
For database application a GUI can be generated automatically. To do so a structure file is required, which describes the database. From the information in this file, for each table the SDK creates matching template files, the appropriate actions and also generates the necessary additions to the interface of the application.
If you don't want to create such a database application, you may skip this step.
This will automatically create for each table: templates to view, edit, create and search database contents, plus the PHP-code for all required functions. In addition functions for downloading images and files from the database. Also a JavaScript file is created, which can be used to trigger actions on the server via Ajax. Last the structure file itself will be stored in the system.
If you have not created a structure file yet, you may now create such a file. For this use the plug-in "DB-Tools". On the administration panel open the menu "DB-Tools" / "Import". Choose one of the options to import an already existing database. If you don't have any, please read the beginner's tutorial for a start. Also note the examples in the Developer's Cookbook.
If you now got a structure file, input the path to this file here. Click on "search", if you are not sure where this file is stored.
Select the source file in SQL format, that contains the information to install the database.
You will find your SQL installation files in the folder "config/db/.install/" after finishing the process. This folder contains subdirectories for all supported DBMS. You may also copy your SQL-files to this location at a later stage. The file must have the same name as the structure file and use the extension ".sql".
To end the configuration, click "complete".
The SDK produces a skeleton of the application, all the necessary templates, as well as all the necessary configuration files. These include in particular: meta information, e.g. description of the plug-in's interface, meta information of required files, meta information of used templates.
The newly generated plug-in can be used immediately. You can find all files in the directories "plugins", "skins/default" under the ID code of the plug-in.
error_reporting(0);to this text:
error_reporting(E_ALL);
ErrorUtility::setErrorReporting(YANA_ERROR_ON);
Many editors may make working with the Yana Framework easier. To aid you with translating, developing new plug-ins, or writing skins, syntax Highlighter and code templates are available for different editors.
The following section is to show you examples, how these tools can facilitate your work.
For editing the configuration files, templates and plug-ins of the Yana Framework in "ConTEXT", syntax Highlighter and code templates are available ConTEXT is a programmer's editor. You can get it from the internet at http://context.cx. The program is free and offers support for far more than 50 programming languages and file formats. Among them the complete Yana Framework.
For the installation of the highlighter and template files for the Yana Framework, install ConTEXT first. Then open the installation directory of the editor and copy the "ConTEXT Highlighter files" (* chl) to the directory "Highlighters" and "ConTEXT Template files" (* ctpl) to the "Template" directory (see figure). If you have ConTEXT open already, please restart the editor now.
Figure: ConTEXT installation directory
ConTEXT offers the possibility of using the manual of the Yana Framework directly in the editor. To use this feature, do as follows:
1. Open the menu "Options" > "Environment Options"
Figure: Open environment options
2. Switch to the menu "Miscellaneous" and choose the entry "Yana Framework" and / or "Yana Framework Templates" from the list.
3. Click the button "edit".
Figure: Associate documentation
4. Select the file type "all files (*.*)", search your local hard disk for the file "index.html" of this manual and mark this file with your mouse.
5. Click on "Open"
Figure: Choosing file "index.html" as front page
5. Save your changes by clicking "OK".
You can get all the required files at the following web sites:
Open a configuration filei (e.g. a database schema at folder yana/config/db/) using ConTEXT and choose the highlighter "Yana Framework".
Figure: Working with database schema files in ConTEXT
Open a template file with ConTEXT and choose the highlighter setting "Yana Framework Templates".
Figure: Working with a template file in ConTEXT
To insert a code snippets use the shortcut <CTRL> + <j>, or choose "Insert Code from Template" in menu "Format". A list will be displayed. It consists of two parts: left you will find the keyboard shortcuts and on the right a description. Select a code template using your mouse or the cursor keys and press <ENTER> to insert it.
You may narrow the selection, by typing the first character of the template. If only 1 templates fits for insertion, the list will not be shown and the template will be inserted directly.
E.g.: fast creation of a database schema file
The result is demonstrated in the following figure.
Figure: Creating a new table the easy way
See the list for more shortcuts.
For editing the configuration files, templates and plug-ins of the Yana Framework in "PSPad", syntax Highlighter and code templates are available ConTEXT is an editor for web developers. You can get it from the internet at http://www.pspad.com. The program is free ware.
PSPad can also be used on an USB device without installation.
To install highlighter and template files for the Yana Framework please install PSPad first. Then open the installation directory of the editor and copy the "Highlighter files" (*.ini) to the directory "Syntax" and "Template files" (*.def) to the "Context" directory (see figure). If you have PSPad open already, please restart the editor now.
Figure: PSPad installation directory
To finish the installation you have to activate the new files in PSPad.
Figure: Activating the highlighter
You can get all the required files at the following web sites:
Open a plug-in definition file (plugins/*.config) in PSPad.
To select the highlighter click the button "Change document syntax highlight" (see figure).
Figure: a configuration file for the Yana Framework opened in PSPad, on the right an active code browser
Select the entry "Yana Framework" or (when editing templates choose "Yana Framework Templates") and click "OK".
Figure: Choosing syntax highlighting
As in ConTEXT you can use prepared blocks of text in PSPad. These are called "Clips" in PSPad and can be viewed by using the keyboard shortcut <CTRL> + <SPACE>.
Use of this feature is identical to that in ConTEXT. A menu will be shown. It consists of two parts: left you will find the keyboard shortcuts and on the right a description. Select a code template using your mouse or the cursor keys and press <ENTER> to insert it.
You may narrow the selection, by typing the first character of the template.
Figure: using "Clips" in PSPad
This program uses so called "templates". What ever you change on these pages will also change the look and feel of your application. These files are stored in the "skins" directory. You can open and edit these files like any normal HTML page in your web editor. For changing the files no knowledge in HTML is necessary, however: it will come in quite handy, if you plan on implementing a larger amount of changes.
In any case you should make copies of the original files, before you apply any changes.
Tutorials on creating your own templates can be found on the at Smarty's web site:http://smarty.net
If you are not just editing an existing template, but want to create a new one - or perhaps even a new skin -, then it is not enough to just edit template files. You must also understand and be able to apply the configuration of templates and skins.
However, this is very easy and limited to two configuration files. One for your new templates and the other for the skin.
Here is a simple example.
<SKIN_INFO> <NAME>my theme</NAME> <DESCRIPTION>description of my theme</DESCRIPTION> <AUTHOR>my name</AUTHOR> <CONTACT>my web site</CONTACT> <LOGO>%SKINDIR%mytheme/data/preview.gif</LOGO> <DIRECTORY>mytheme/</DIRECTORY> </SKIN_INFO>
The syntax is largely self-explanatory. The fields "Name", "Description", "Author" and "Contact" are free text fields. For the "name" you can have any name, which describes your theme. The same applies to the field "Description". Make sure that the only allowed whitespace characters are simple spaces. You can add a line break, if necessary, by writing the text "[br]". The field "Author" should be your name and "Contact" usefully an e-mail or Internet address, where the user may view your web site or download updates.
The field "Logo" is a preview image. The place holder %SKINDIR% refers to the directory in which the skins are saved and should always be used. The graphic should preferably be in GIF, PNG or JPEG format, and about 300x300 pixels in size.
The "Directory" must always be specified. It names the directory in which you theme is stored.
Here is a simple example.
<MY_TEMPLATE> <FILE>template.html</FILE> <STYLE> <0>style/default.css</0> <1>foo1/foo2.css</1> <MY_CSS>foo3/foo4.css</MY_CSS> </STYLE> <SCRIPT> <0>foo5/foo6.js</0> <MY_SCRIPT>foo7/foo8.js</MY_SCRIPT> </SCRIPT> <LANGUAGE> <0>guestbook</0> <1>admin</1> </LANGUAGE> </MY_TEMPLATE>
Note that "MY_TEMPLATE" acts as an identifier, which identifies the template. You can use this Id in the configuration files for your plug-ins to refer to the template. The text can be chosen arbitrarily. However, be careful that the Id contains no special characters or spaces, it must begin with a letter and must by unique throughout the entire application. The Id is not case-sensitive.
There are 4 fields: "File", "Style", "Script" and "Language". The field "File" names the file name. The other fields are optional and serve automation.
The field "Language" contains references to one or more files, which provide text strings in various languages for this template. For example, the value "guestbook" refers to the file "languages/XX/guestbook.config", where the text "XX" is automatically replaced by the system with the name of the user's language of choice. E.g. "de" for German or "en" for English.
The field "Style" can contain multiple references to stylesheets. Similarly, the field "script" contains links to one or more JavaScript files. These files are automatically included when the template is called.
Notice: If in a skin a required template is not defined, or properties of this template ( "Styles", "script", "Languages") are not declared, the program automatically falls back to the "Default"-skin settings.
This is done as follows.
This behavior may seem illogical at first, but has a quite a deeper meaning. It allows to exchange individual style sheets, or scripts in a newly created skin. At the same time it also allows individual style sheets, or scripts to be excluded from this mechanism. Both variants are frequently used and have their own application areas.
Another example to illustrate this behavior. For the Template "MY_TEMPLATE", the following definition exchanges only the CSS file "MY_CSS" defined int the default skin with another file. All other settings remain unchanged.
<MY_TEMPLATE> <STYLE> <MY_CSS>foo/bar.css</MY_CSS> </STYLE> </MY_TEMPLATE>
As you can see in this example, no new HTML template is defined, but the setting of the default skin is used. This means that there is no need for the HTML template from the default-skin to be copied to the new skin, or even edited, to change the layout. This avoids unnecessary redundancy and makes it easier for you to maintain your HTML code.
* "required" means in this case "unequal NULL"
Wildcard | Type | Required * | Default value | Origin | Description |
---|---|---|---|---|---|
PHP_SELF | String | yes | - | PHP | Name of the executed script file |
REMOTE_ADDR | String | yes | - | PHP | IP address of client. |
SETTINGS.WEBSITE_URL | String | no | - | calling URL | complete URL of the script (incl. http://...) |
REQUEST_URI | String | no | - | calling URL | called path of the script, including parameters (/yana/index.php?...) |
QUERY_STRING | String | no | - | calling URL | list of parameters passed to the script |
ID | String | yes | default | GET/POST | ID-Code of the current profile. |
ACTION | String | no | <empty> | GET/POST | Name of the currently (to be) executed action. |
TARGET | String | no | <empty> | GET/POST | Target of the currently executed action. (e.g. the ID of a data set) |
SESSION_NAME, SESSION_ID | String | no | <empty> | GET/POST | Name and ID-Code of the current session (should be added to all hyperlinks) |
PAGE | integer | no | 0 | GET/POST | The number of the data set from which the show started. (If required) |
ATIME | string | yes | - | File property | Insert date and time when the HTML-template was last accessed at this place: [%$ATIME|date%] |
MTIME | string | yes | - | File property | Inserts at this place the date, when the HTML-template was last changed: [%$MTIME|date%] |
CTIME | string | yes | - | File property | Inserts at this place the date, when the HTML-template was created: [%$CTIME|date%] |
LANGUAGE | Array | yes | - | Language file(s) | List of all text strings defined in the currently selected translation. |
PROFILE | Array | yes | - | Profile file | List of all settings in the currently used profile. The following are some examples. Note that all fields mentioned here may also contain a NULL value. |
PROFILE.BGCOLOR, PROFILE.BGIMAGE |
String | no | - | Profile file | Background color and image of the web site |
PROFILE.PFONT, PROFILE.PSIZE, PROFILE.PCOLOR |
String | no | - | Profile file | font family, size, color of paragraphs (P-tags) |
PROFILE.HFONT, PROFILE.HSIZE, PROFILE.HCOLOR |
String | no | - | Profile file | font family, size, color of headings (H1-,H2-,H3-tags) |
SKIN_INFO | Array | no | - | Skin file | Informations on the selected Skin |
LANGUAGE_INFO | Array | no | - | Language file | Informations on the selected language |
LANGUAGE | Array | no | - | Language file(s) | Translations (texts) |
SETTINGS.WEBSITE_URL | String | no | - | Language file(s) | complete URL of the script (incl. http://...) |
This Framework uses the "Smarty Template-Engine". This engine has it's own documentation, which you can download from the internet for free at http://smarty.net/docs.php.
You are permitted to expand the engine with your own functions. In this way, you can extend the syntax of the templates used You will find the engine in the directory "engine/". A guide can be found in the reference manual of the engine. Changes to the engine, are only recommended for experts.
About licenses: Before you make any changes, please note the license information.
The syntax of the used version of "Smarty template engine" differs in some aspects from that described in the reference manual. The following section describes this modified syntax.
Unlike in the original version of Smarty, this version does not use the characters'{' and '}' as the opening and closing delimiters of Smarty codes, but the strings: '[%' and '%]'. For example, NOT "{$variable}", but "[%$variable%]". The reason is that the original characters are frequently used in many HTML documents, as they may contain scripts or stylesheets, which may cause problems when processing the templates. This is why the delimiters have been changed. (This is also recommended by the manual of the engine as a standard solution for any such cases.)
Unlike in the original, this version also allows the use of an abbreviated syntax for inserting simple variables. Instead of "[%$VARIABLE%]" this version allows the more comfortable syntax: "%VARIABLE%". However, this only applies to variables. This can NOT be used with any other Smarty-codes.
Because Smarty codes are case-sensitive, it has been decided to write all variable names in capital letters for simplicity. While the names of functions and other codes are to be written in small letters. For the sake of clarity, is strongly recommended, to keep this convention.
In this version references to files (links, graphics, CSS, scripts, etc.) can be given as relative path information. File paths are automatically corrected when the page loads. It is therefore no longer necessary, to use absolute file paths only.
Smarty is set to run in Safe Mode (security=true). The embedding of PHP code into templates is not permitted. In addition, the following features are disabled: include, include_php, php.
[%captcha [ id="<string>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
id | string | no | - | Value of the attribute "id" of the tag "input" |
The function "captcha" is used to view a graphic, which contains text, that the user must read and enter to the input field next to it. This is a common process to avoid spam.
The parameter "id" is optional. For example, it can be used, when a description is to be shown in a "label".
[%captcha%]
Figure: Display in browser
<label for="myCaptcha">Please enter the text on the image</label>
[%captcha id="myCaptcha"%]
For further details see also the Developer's Cookbook, chapter "Using a CAPTCHA".
[%create template="<string>" file="<string>" table="<string>" [ where="<string>" ] [ desc="<boolean>" ] [ sort="<string>" ] [ page="<integer>" ] [ entries="<integer>" ] [ titles="<boolean>" ] [ on_new="<string>" ] [ on_edit="<string>" ] [ on_delete="<string>" ] [ on_search="<string>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
template | string | yes | - | Name of template to be used when creating the GUI. Allowed values are: view_seperated, view_details, view, new, edit, search |
file | string | yes | - | Path of the structure file which is to be used. |
table | string | yes | - | Name of the table which is to be used. |
show | string | no | - | Comma-separated list of columns of the table, which should be displayed (use small letters) |
hide | string | no | - | Comma-separated list of columns of the table, which should be NOT displayed (use small letters) |
where | string | no | <empty> | Used to generate the WHERE-clause for the SQL-statement. Syntax: <COLUMN1>=<VALUE1>[,<COLUMN2>=<VALUE2>[,...]] |
desc | boolean | no | false | Selects whether entries are to be sorted in ascending (false) or descending (true) order. |
sort | string | no | <primary key> | Name of column to sort entries by. |
page | integer | no | 0 | Number of first entry to be shown. |
entries | integer | no | 5 | Total of shown entries. |
titles | boolean | no | true | Selects whether attribute names are to be shown or not. |
on_new | string | no | - | Name of action to be triggered, when a new entry is to be saved. |
on_edit | string | no | - | Name of action to be triggered, when changed entries are to be saved. |
on_delete | string | no | - | Name of action to be triggered, when chosen entries are to be deleted. |
on_search | string | no | - | Name of action to be triggered, when a search request is to be handled to create a hit list. |
on_download | string | no | download_file | Name of action to be triggered, if the user wants to download a file or image. |
layout | int | no | 0 | If a form has multiple ways to be displayed, you can use this parameter to switch between them. |
The function "create" automatically generates a GUI for the chosen database. Condition is the existence of a valid structure file, which describes the database.
[%create template="edit" file="database.config" table="bookstore"%]
An example of a function to download a file (argument on_download "):
<?php function myDownload($ARGS) { if ($source = DbBlob::getFileId() === false) { return false; } // Downloading a file: if (preg_match('/\.gz$/', $source)) { $dbBlob = new DbBlob($source); $dbBlob->read(); header('Content-Disposition: attachment; filename=' . $dbBlob->getPath()); header('Content-Length: ' . $dbBlob->getFilesize()); header('Content-type: ' . $dbBlob->getMimeType()); print $dbBlob->get(); // Downloading an image: } else { $image = new Image($source); $image->outputToScreen(); } exit; } ?>
Attention! This function does not automatically check, whether the user is "allowed" to download the selected file in the sense of your program. This is unfortunately not possible without knowing the criteria that you want to implement in your program. Therefore, it remains at this point to you to verify that the user has the necessary permissions to view the file.
[%embeddedTags [ show="<string>" ] [ hide="<string>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
show | string | no | * | comma-separated list of tags, that are to be shown |
hide | string | no | - | comma-separated list of tags, that are to be hidden |
The function "embeddedTags" automatically generates buttons, that allow users to use text formatting (bold, italic, etc.) when writing a new entry.
The list "show" can be used to select which tags should be displayed, while the list "hide" can be used if you want to hide certain tags.
In the list "show", you can use the characters "-" to insert line breaks and the characters "|" to create a vertical separator in the output.
<textarea id="text"></textarea>
Figure: Displaying a tag bar (wih all tags)
<!-- Include all tags -->
[%embeddedTags%]
<!-- Display tags "u", "i" and "b" only -->
[%embeddedTags show="b,u,i"%]
<!-- All tags except "url" and "mail" -->
[%embeddedTags hide="url,mail"%]
<!-- Tags "u","i","b", a seperator, then the tag "url" -->
[%embeddedTags show="u,i,b,|,url"%]
[%import [preparser="true"] file="<string>"%]
[%import [preparser="true"] id="<string>"%]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
file | string | yes | - | Path of the file to be included |
preparser | boolean | no | false | selects whether the file should be imported before (true) or while (false) parsing the template |
Or alternatively with the following parameters:
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
id | string | yes | - | Id of the template to be included |
preparser | boolean | no | false | selects whether the file should be imported before (true) or while (false) parsing the template |
The function "import" replaces the various file-insertion-functions of the original by one single function. It also differs in the way the files are treated. The function "import" treats each imported file as a separate template. This has several advantages: the inserted template uses e.g. security restrictions as the base template. In addition (and more importantly:) this approach ensures that any given relative paths can be corrected / changes properly.
The Parameter "file" is the name of the file to be imported.
Alternatively, the parameter "id" can be used, so instead of hardwiring the path and file name directly in the template, you may use the Id of the template. The framework resolves the Id to get the appropriate file name automatically at runtime. This has the advantage, that if the file is renamed, or moved to a different path, you don't need to check all templates by hand to correct all references to this file.
The flag "preparser" determines when a file is to be imported: BEFORE or DURING the parser handles the template. If it set, the file is imported BEFORE the actual parsing. The flag "preparser" for example, should always be used if the imported templates need to have access to variables that are set in the base template. A classic example are templates, that are imported inside the body of a loop. ( "Foreach", "section) These require the flag "preparser", if you need to access loop variables. Alternatively, you can use the statement "import" with a list of all the variables you want to access within the template.
Attention! If the parameter "preparser" is used, the imported template is not parsed, translated and cached on it's own, but copied in the source code of the template that contains the import statement. This means, it is not checked at runtime, if the content of the imported template has changed since when the templates was last compiled. If you change the imported template, you must empty the template cache for the changes to take effect.
File "hello_world.html"
Hello World!
File "template.html"
<p>[%import file="hello_world.html"%]</p>
File "default.config"
<hello>
<file>hello_world.html</file>
</hello>
File "template.html"
<p>[%import id="hello"%]</p>
File "hello_world.html"
[%if $foo%]
[%$bar%]
[%/if%]
File "template.html"
<p>[%import file="hello_world.html" foo=true bar="Hello World!"%]</p>
<p>Hello World!</p>
[%preview [ width="<integer>" ] [ height="<integer>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
width | integer | no | 380px | Width in pixel |
height | integer | no | 50px | Height in pixel |
The function "preview" creates a preview window on an entry, that the visitor wrote.
Smilies and tags contained in the text are graphically displayed. This gives the visitor the opportunity to check how his entry would be displayed, before he submits the form.
When you call the function all required JavaScript files will be loaded automatically. In addition a button labeled "Preview" will be created. When this button is clicked a preview window will be displayed above the button.
<textarea id="text"></textarea>
[%preview%]
Figure: The preview will be loaded by clicking the button
"Preview" via an "AJAX HTTP-Request" to the server
Figure: Preview window as it is shown in a browser
The attributes "width" and "height" set width and height of the preview window. These values are interpreted as CSS instructions. So if you want to use this, please note that it is required to add the unit of measurement (for example, "px" for pixels). Percentage values, like "70%" are allowed as well. Numbers without a unit, like "70" may however be displayed incorrectly be the browser.
These values do not refer to the entire size of the preview window, but to the text displayed inside it. The following figures demonstrates this:
Figure: Margins of box
The purple colored area here is the space defined by the attributes "width" and "height". The light green area is the total size of the field. The space required for the heading is shown in light blue. The border is indicated in violet. So the total size of the window depends on the values of the attributes "width" and "height", plus the heading, the border and the peripheral area (margin) of the window.
[%printArray value="<array>" %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
value | array | yes | - | an array, that is to be printed as a string |
This functions converts a (possibly multidimensional) array to a string and prints it. The array is displayed in SML-format. This can, inter alia, be of interest for the debugging of templates, to find out the name of a variable.
Note: Since version 2.9.3 of the Yana Frameworks this function supports syntax highlighting.
[%printArray value=$LANGUAGE%]
Figure: a short excerpt from the output
[%printUnorderedList value="<array>" [ keys_as_href="<bool>" ] [ layout="<int>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
value | array | yes | - | an array, that is to be printed as a list |
keys_as_href | bool | no | false | selects, whether links are to be created |
layout | int | no | 1 | lets you chose between 3 alternative layouts (1, 2 and 3) for your menu |
This functions converts a (possibly multidimensional) array to an unsorted list in HTML-format and prints it. It uses the tag "ul".
You can use this function to create tree menus. You may chose the create links within this menu. To do so, use the URLs as a key of the array and the caption as values. Moreover set the parameter "keys_as_href" to "true".
[%printUnorderedList value=$LANGUAGE%]
Figure: a short excerpt from the output
2. Example ? output a tree menu with links:
<?php $A = array( '1.html' => 'Link', 'Menu 1' => array( '2_1.html' => '1. Item', '2_2.html' => '2. Item', 'Menu 2' => array( '2_3_1.html' => '1. Item', '2_3_2.html' => '2. Item' ), ), 'Menu 3' => array( '3_1.html' => '1. Item', '3_2.html' => '2. Item', '3_2.html' => '3. Item' ), ); $YANA->setVar('A', $A); ?>
[%printUnorderedList value=$A keys_as_href="true"%]
Figure: Displaying a tree menu with links
3. Example ? additional layouts:
[%printUnorderedList value=$A layout=2 keys_as_href="true"%]
Figure: A vertical tree menu
[%printUnorderedList value=$A layout=3 keys_as_href="true"%]
Figure: a horizontal tree menu
The keys are printed in bold and values in italics - however, this may be changed via CSS. The following CSS classes are available:
[%rss [ image="<string>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
image | string | no | - | The icon, which the visitor can click to view the RSS feed. |
This function creates hyperlinks to view the RSS feed of a web site.
The argument "image" can be used to select another icon. If the user clicks on this link, the file is opened. The list of the RSS feeds will be inserted in the source code of the application. See also the Developer's Cookbook, Chapter: "Misc. functions".
Tip: Even if you do not create hyperlinks, your visitors are still able to read the RSS feeds of your web site, because the links are also provided in the header of the file. This information allows the browser of the visitor, if it is able to, to find and read the feeds. However, this variant is more cumbersome for your guests and therefore should be avoided.
[%rss%]
Figure: Output of this function
Example ? adding a feed to the output:
<?php // The value 'my_rss' is the name of the function, that outputs the RSS feed RSS::publish('my_rss'); ?>
Note: In the skins provided with this software, the creation of hyperlinks to RSS-feeds works automatically, so that there is generally no need to include this feature by hand, unless you decide to write your own skin theme. In this case you should include the hyperlinks in the head area of the site, where they can be noticed by visitors.
[%sizeOf value="<mixed>" [ assign="<string>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
value | mixed | yes | - | scalar value or array, whose length is to be counted |
assign | string | no | - | Name of the template var, that result should be saved to |
The function "sizeOf" returns the length of an array or a string as a number. If the parameter "assign" is specified, then the return value is stored in the named variable and the function does not return a result.
<td colspan="[%sizeOf value=$cols%]"> ... </td>
[%sizeOf value=$cols assign="foo"%] <td colspan="[%$foo%]"> ... </td>
[%smilies [ width="<integer>" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
width | integer | no | 1 | Number of columns in a table |
The function "smilies" automatically generates a table of all available emot-icons / smilies, with a link to paste them into a specified input field.
<textarea id="text"></textarea>
[%smilies width="5"%]
Figure: Table of emot-icons (5 per row)
[%smlLoad file="<string>" [ section="string" ] [ scope="string" ] %]
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
file | string | yes | - | Number of columns in a table |
section | string | no | - | ID of an input- or text area field |
scope | string | no | local | Valid values: "local", "global", "parent", "template_vars" |
global | boolean | no | false | deprecated |
This function provides the same interface as the Smarty function "config_load". However, this function works with SML files with the file extensions "*.sml", "*.cfg" or "*.config".
Unlike the conventional configuration files of the Smarty engine, SML files may use multidimensional array definitions.
Moreover, the argument "scope" may have the value "template_vars", which means that the variables of the SML file can be used as template variables - this allows to access multidimensional arrays in standard smarty syntax, like any other variable. This feature only applies to smlLoad() and not to config_load().
The directory "skins/.config" contains a file "foo.sml". It has the following contents:
<TEST>
<A>1</A>
<B>
<C>2</C>
</B>
</TEST>
The template contains the following:
[%smlLoad file="foo.sml" section="TEST.B" scope="template_vars"%]
[%$C%]
Output: 2
[%smlLoad file="foo.sml" section="TEST.B"%]
[%#C#%]
Output: 2
[%smlLoad file="foo.sml" scope="template_vars"%]
[%$TEST.B.C%]
Output: 2
The function name "sml_load" is an alias for the function "smlLoad". You can use both names alternative.
[%varDump var="<mixed>" %]
Note: This function is only meant to be used for debugging purposes. It is only available, if the framework is run in debug mode. If this mode is deactivated, any try to call this function will result in an error. This serves the security of your application.
Attribute | Type | Required | Default value | Description |
---|---|---|---|---|
var | mixed | yes | - | Value to be output |
The function "varDump" outputs the value of a variable.
[%varDump value=$INSTALLED_LANGUAGES%]
array ( 'de' => 1, 'en' => 1 )
[%$foo|css%]
Creates a link to a given path to a CSS-Datei.
[%"default.css"|css%]
Output:
<link rel="stylesheet" type="text/css" href="default.css">
[%$foo|date%]
From a variable containing an UTC, this modifier creates a JavaScript statement, that outputs the current time using the correct time zone of the client browser.
Template created: [%$CTIME|date%]<br>
Template modified: [%$MTIME|date%]<br>
Template last accessed: [%$ATIME|date%]<br>
Output:
Template created: 1/12/2006
Template modified: 19/1/2007
Template last accessed 21/1/2007
[%$foo|embeddedTags%]
This modifier replaces all embedded tags in a variable by the appropriate tags, as prescribed by YANA's syntax.
$foo = "Text [b]Text[/b] Text"
Output:
Text <span style="font-weight: bold;">Text</span> Text
[%$foo|href%]
Generates a link to the file index.php including all required parameters. The string $foo is appended to the end of the URL The URL will automatically be embraced by double quotation marks (").
<a href=[%"action=myplugin_new_entry"|href%]>new entry</a>
Output:
<a href="index.php?sessid=foo&id=example&action=myplugin_new_entry">new entry</a>
[%$foo|smilies%]
Replaces all icon codes in a variable by the appropriate icons.
$foo = "Text :example: Text"
[%$foo|smilies%]
Output:
Text
<img border="0" hspace="2"
src="common_files/smilies/example.gif">
Text
[%$foo|url%]
Generates a link, just as the modifier "href", but outputs an absolute path without quotation marks.
<meta http-equiv="Refresh" content="2; URL=[%"action=myplugin_new_entry"|url%]">
Output:
<meta http-equiv="Refresh" content="2;
URL=http://.../index.php?sessid=foo&id=example&action=myplugin_new_entry">
Attention! The following will not work:
<a href="[%"action=foo"|url%]">link</a>
Result: <a href="skins/default/http://.../index.php?...">link</a>
[%$foo|urlEncode%]
Encodes a text so that can safely be used as parameters in a URL. Therefore it will use the PHP function urlencode()
.
<a href="?bar1=foo1&bar2=[%$foo|urlEncode%]">
Output:
<a href="?bar1=foo1&bar2=foo%20bar">
For the Yana Framework all web applications are called "plugins". More complex applications may consist of several sub-systems. These subsystems may have several plug-ins, which work hand in hand. Some of these "subsystems" are already pre-implemented and only need to be used.
In this way, programmer's may save a lot of work. For example, you don't need to worry about checking passwords. There are already plug-ins, which will do this for you. Also the administration menu does not need the be rewritten, instead it just needs to be used. If you don't like a component, just replace or remove it. If a feature is missing, it can be added without changing to other plug-ins.
The creation of a new plug-ins will become simpler, if you are using YANA's SDK. It is a wizard, that gathers all necessary information and then automatically creates the new application for you. You simply have to adapt the result to your needs.
First some general information has to be given. See figure above.
Some information on you, the author of the software:
The second step allows developers to add their own actions to the plug-in's interface.
If you just want to create a simple database application, you may skip this step. Most standard actions, such as the viewing of database entries, search, edit, delete or insert don't need to be written by hand. These actions are created by the SDK automatically.
If you want to set up other actions, you can define their interface here. To do so you may use one of the templates, which the SDK suggests. A manual creation is also possible. The interfaces can also be manually changed.
For some actions their are standard templates, which can be used. Have a look at the select box. If an option is selected, a list of all generated actions is shown for comparison. You can review the entire interface by selecting the tab "edit interface manually".
An interface contains the following information:
This option may be used to control the results, or as an alternative input interface for experienced users. You see all defined actions and can make changes.
Beware! Errors may cause, that the plug-in cannot be generated correctly.
For database application a GUI can be generated automatically. To do so a structure file is required, which describes the database. From the information in this file, for each table the SDK creates matching template files, the appropriate actions and also generates the necessary additions to the interface of the application.
If you don't want to create such a database application, you may skip this step.
This will automatically create for each table: templates to view, edit, create and search database contents, plus the PHP-code for all required functions. In addition functions for downloading images and files from the database. Also a JavaScript file is created, which can be used to trigger actions on the server via Ajax. Last the structure file itself will be stored in the system.
If you have not created a structure file yet, you may now create such a file. For this use the plug-in "DB-Tools". On the administration panel open the menu "DB-Tools" / "Import". Choose one of the options to import an already existing database. If you don't have any, please read the beginner's tutorial for a start. Also note the examples in the Developer's Cookbook.
If you now got a structure file, input the path to this file here. Click on "search", if you are not sure where this file is stored.
Select the source file in SQL format, that contains the information to install the database.
You will find your SQL installation files in the folder "config/db/.install/" after finishing the process. This folder contains subdirectories for all supported DBMS. You may also copy your SQL-files to this location at a later stage. The file must have the same name as the structure file and use the extension ".sql".
To end the configuration, click "complete".
The SDK produces a skeleton of the application, all the necessary templates, as well as all the necessary configuration files. These include in particular: meta information, e.g. description of the plug-in's interface, meta information of required files, meta information of used templates.
The newly generated plug-in can be used immediately. You can find all files in the directories "plugins", "skins/default" under the ID code of the plug-in.
To test the just-created plug-in:
This application uses files to store content, which can be translated to several languages. These files are grouped together as packages. One package fits to one translation. For example, this might be a German or an English package. Each package contains texts and graphics. In the text files, IDs are assigned texts. The IDs are identical to the corresponding tokens in the skin packages.
If you have found an error in a translation package and want to correct it, you will find the appropriate files in the directory "languages/". One subdirectory per package. Each of the packages contains files which match the names of the plug-ins which they are intended to be used with.
Editing these files is possible easy. Open the file in a text editor of your choice. The files use a XML-like format. Avoid the use of HTML tags in these documents. Using embedded tags is permitted. Note that no line breaks are permitted within the text flow. To insert a line break, use the embedded tag [br].
If you edit a translation, it is recommended to overwrite the original data. Instead make a copy of the file as a new language pack.
To translate the text messages of the application to a new language, save a copy of the directory "languages/de" and the related configuration file "deutsch.config" under a new name. You should use the name of the language for which you want to provide the translation. For example "languages/es".
You can add your personal information, such as name of the author, or a brief description. Edit these settings in the configuration file of the language package. You can also insert an appropriate graphic. Usually a national flag. To do so replace the file "flagge.gif". The name of the file is specified in the configuration file under the id "LANGUAGE_INFO.LOGO".
"Coding principles" are general rules on how to write and format PHP-code. These rules are meant to help, to keep your source code easy to read and understand.
If several authors are working together on the same source code, it is quite common, that each author may have it's own idea on how clean code should look like. There are dozens of possibilities to express the same thing with PHP. To ensure the source code remains well readable in the long term, it is necessary that all authors agree on a common standard on how to write statements.
The "Coding principles" stated here provide such a definition. These rules are not obligating, however, I would like to recommend them to you.
The source code should be indented with 4 blanks. Do not use tabulators, because these can be viewed differently on different systems.
For comments use exclusively the syntax /* comment */. This has a deeper meaning! Later, when you write or use scripts, that analyse or automatically document your source code, this will make your task a lot easier. Unlike the syntax // comment or # comment, you have a fixed start and end for each comment, that you can rely on. Lines of comments can be fetched easily with simple regular expressions, saving you much effort when creating these scripts.
Here is a example.
<?php $output = array(); /* iterate through input */ foreach ($input as $key => $element) { $element = strip_tags( (string) $element); if (preg_match("/\//", $key)) { $key = preg_replace("/\//", ".", $key); Hashtable::set($output, $key, $element); } else { $output[$key] = $element; } } /* end foreach */ ?>
In PHP has no variable declaration in the sense of other languages. Nevertheless variables have a certain life span. Before and after a variable's life span it does not exist, what means no value is assigned.
The reason for many errors in PHP is, that programmers assume a variable to be unused, of a certain type, or that is no longer used after a certain code line. However PHP does NOT ensure that and also will not warn you if you are wrong, unless you "please" it to do so. This is exactly what you should do!
If you do it, PHP will warn you, if you use a variable outside it's life span and by doing so will possibly save you many hours of debugging!
Variables should be named in CamelCase - for example, $debugSourcePath instead of $debug_source_path. The first letter should always be written small - in difference to class names. There (and only there) the first letter should be large. This definition for the way of writing is similar to that in many other languages like Java.
<?php /* Start of life span */ /* The following line will warn you if the variable already exists */ assert('!isset($variable); // Cannot redeclare $variable'); /* always initialize variables before use */ $variable = 0; /* ... additional statements ... */ /* If a variable is no longer needed it should always explicitly be unset */ unset($variable); /* End of life span */ ?>
When working with if and else, please always use brackets { } for each block. While PHP permits to spare these brackets, if a block consists of only one statement, this style of coding is error-prone, since you may always come to the point where you decide to add another line of code and in this situation it is easy to oversee the missing brackets.
If an If-block is very long (more than 10 lines), you should add a comment /* end if */ at the closing bracket for better readability. For long code blocks this helps to identify what statement a bracket belongs to.
<?php if ($test1) { /* ... */ } elseif ($test2) { /* ... */ } else { /* ... */ } /* end if */ ?>
When using Switch-statements mind the text indentation and line breaks, which serve readability.
Avoid using "fall trough", for this may lead to code which is difficult to read. If you somehow cannot avoid it, you should mark this special case with the comment /* fall through */ instead of the Break-statement for documentation.
The Default-case should always be included. This enhances the clarity of your code.
If two different cases use the same code, this can be expressed by writing case 1: case 2: ... break;. This is a useful abbreviation and you are recommended to use it, where you see fit.
<?php switch ($variable) { case 1: /* ... */ break; case 2: case 3: /* ... */ break; case 4: /* ... */ break; case 5: /* ... */ break; case 6: /* ... */ /* fall through */ default: /* ... */ break; } /* end switch */ ?>
Again mind the line breaks for better readability.
PHP has a rather uncommon "feature". The life span of a block / loop -variable does NOT end, as in other languages, when exiting the block / loop, but will continue to have it's last assigned value. This also means that loop variables in PHP may overwrite the value of another, already existing variable. PHP will not warn you on this issue. To address this behavior, and the potential risk, which is associated´with it, you should treat the begin of any For- or ForEach-loop like a variable declaration and check whether any used loop variables already exist. When exiting a loop, you should always explicitly unset variables to avoid side effects.
Note that a ForEach statement necessarily expects an array to work on. Before entering the loop check the type of the input variable by using the function is_array () and add appropriate error handling if the variable has not the expected type.
For While-loops ensure that exit points, introduced by Break- or Continue-statements, are always sufficiently documented.
Take a look at the following examples:
<?php /* Example: FOR-loop */ assert('!isset($i); // Cannot redeclare $i'); for ($i = 0; $i < sizeOf($input); $i++) { /* ... */ } /* end for */ unset($i); /* Example: FOREACH-loop */ assert('!isset($key); // Cannot redeclare $key'); assert('!isset($element); // Cannot redeclare $element'); foreach ($input as $key => $element) { /* ... */ } /* end foreach */ unset($key); unset($element); /* Example: WHILE-loop */ while ($test) { /* ... */ } /* end while */ ?>
For the names of classes and functions be advised to uniformly use CamelCase. For function names, the first letter is always small.. For class names the first letter is always capitalized. This rule is similar to Java and other C-type languages.
In PHP it is common that the names of private functions and methods start with an underscore to improve readability. If you decide to use this notation, please use it only for identification of private functions and methods.
To improve readability it is recommended, that the name of a function starts with a verb in imperative and a noun in singular, for example: "getBeer()" or "setContent()". If the return value is a list object or an array, use a noun in plural, for example: "getLines()".
Use PHPDoc comments (http://www.phpdoc.org/) for documentation of classes and functions. These should be mandatory. Make it a habit to always immediately document any new function you write, while the idea and details are still fresh in your memory. By writing documentation you are forced to review the content of your work and thus will recognize mistakes, false assumptions or defective pre-conditions earlier.
The documentation of a function should always contain: the input data the function expects (for example: "an integer between 0 and 5"), the purpose of the function - that is what this does. Moreover, the return values of the function (for example: "an integer between 1 and 6, or false, if an error occurs").
It is general practice to document source codes in English. If you are able to write your documentation in English, then do so.
If you want to use UML-style stereotypes, then write them in front of the name of the function or class and use parentheses, which is common for writing stereotypes.
It is common to keep variables "private" and the restrict all access to set - and get-functions. For example, access to a private variable "$foo" should be restricted to functions with the name "setFo ()" and "getFoo()". Note that the class variables must always be initialized - if necessary, initialize them with the constant "null"!
<?php /** * «Stereotype» name * * The purpose of this class is ... * * @access public * @package package * @subpackage subpackage */ class MyClass extends BaseClass { /**#@+ * @access private */ /** @var array */ private $var1 = array(); /** @var int */ private $var2 = 0; /** @var View */ private $var3 = null; /** @var array */ private $var4 = array(); /** @var array */ private $var5 = array(); /**#@-*/ /** * «Stereotyp» Constructor * * This function accepts the following input ... * * This function does ... * * It returns the following values ... * * @param string $filename path to file.ext */ public function __construct ($filename) { /* ... */ } /** * «Stereotype» name * * This function accepts the following input ... * * This function does ... * * It returns the following values ... * * @access public * @param string $id alphanumeric, unique identifier * @return int */ public function getVar ($filename) { /* ... */ } } ?>
The API documentation contains information for developers who want to write their own extensions. Here are descriptions of interfaces and examples to all classes and functions of the software.
The Yana Framework offers a API for working with databases, which is based on PEAR-DB. This API extends the abilities of PEAR by the following features:
There are several features which the flat file database currently does not provide, but are planned for future versions.
There are several features which database schemata currently do not provide, but are planned for future versions.
This requires a schema file. The schema files has to be present in the folder "config/db/" and must have the file extension ".config".
<?php global $YANA; $structure = 'guestbook'; $db_connection = $YANA->connect($structure); ?>
The connecting data for the database (like host address, user name and password) is entered by the user in the administration menu. So you DON'T have to include this in your code.
If you don't want to use a structure file, you may leave it blank. However: not that this is not recommended within a productive environment. In this case the framework will try to determine the missing information itself. If this fails the database connection will not be usable.
If you want to open a connection to a database, but want to set the connection information yourself in the code, proceed as follows:
<?php /* connection information is provided as associative array: */ $connection = array( 'DBMS' => 'mysql', 'HOST' => 'localhost', 'PORT' => 0, 'USERNAME' => 'user', 'PASSWORD' => 'pass', 'DATABASE' => 'database_name' ); /* To use the YANA-API to communicate with the database, write: */ $db_server = new DbServer($connection); $yana_api = new DbStream($db_server); /* To use the PEAR-API to communicate with the database, write: */ $db_server = new DbServer($connection); $pear_api = $db_server->get(); /*To use the PEAR-API with the default connection data, write: */ $db_server = new DbServer(); $pear_api = $db_server->get(); ?>
<?php if (YANA_DATABASE_ACTIVE === true) { print "Database is active"; } else if (YANA_DATABASE_ACTIVE === false) { print "Database is NOT active"; } ?>
Therefor the API offers the function $db->get(string $key). This executes a SELECT query on the database and returns the value indicated by the argument $key.
<?php global $YANA; $db = $YANA->connect('guestbook'); /* The following syntax may be used: $db->get( string "$table.$row.$column", string $where, string $order_by, int $offset, int $limit ); Example: $value = $db->get("table.1.field","row1=val1,row2=val2","row1",0,1); will create the following SQL statement: SELECT field from table where primary_key = "1" and row1 like '%val1%' and row2 like '%val2%' order by row1 limit 1; */ /* output field */ $value = $db->get("table.1.field"); /* will create the following SQL statement: SELECT field from table where primary_key = "1"; */ /* output column: */ $column = $db->get("table.*.field"); foreach ($column as $row => $value) { print "<p>Value of 'field' in row '$row' = $value</p>"; } /* will create the following SQL statement: SELECT field from table; */ /* output row: */ $row = $db->get("table.2"); foreach ($row as $column => $value) { echo "<p>Value of column '$column' in row '2' = $value</p>"; } /* will create the following SQL statement: SELECT * from table where primary_key = "2"; */ /* output table: */ $table = $db->get("table"); foreach ($table as $index => $row) { foreach ($row as $column => $value) { echo "<p>Value at 'table.$index.$column' = $value</p>"; } } /* will create the following SQL statement: SELECT * from table; */ ?>
Use the function $db->insert($key,$value). This will insert the value "value" at position "key". This may either be a row or a cell. The insertion of whole tables or columns is not supported.
With the first call of this function a transaction is created automatically. Use the function $db->write() to send a COMMIT. If any of the statements fail a CALLBACK is send automatically.
If the row does not exist, a INSERT statement will be created, otherwise a UPDATE statement.
The function returns "true" on success and "false" otherwise.
Please note: the SQL-statement is not executed unless you call $db->write().
Take a look at the following examples:
<?php global $YANA; $db = $YANA->connect("guestbook"); /* insert new row: */ $db->insert("table.*",array("row1"=>"val1","row2"=>"val2")); $db->write(); /* update row: */ $db->update("table.2",array("row1"=>"val1","row2"=>"val2")); $db->write(); /* update cell: */ $db->update("table.2.row1","val1"); $db->write(); /* execute transaction: */ $db->insert("table.*",array("row1"=>"wert1","row2"=>"wert2")); $db->insert("table.*",array("row1"=>"wert3","row2"=>"wert4")); $db->update("table.1.row3","wert1"); $db->write(); ?>
Use the function $db->remove($key). This will remove the data set "key" from the table. The function returns "true" on success and "false" otherwise. Only rows can be deleted. No tables, cells or columns.
Note the following restriction: for security reasons only 1 row is deleted with each call. To delete more rows, call the function repeatedly. This restriction is to prevent that someone can delete an entire table by inadvertence or by mistake.
Please note: the SQL-statement is not executed unless you call $db->write().
<?php global $YANA;
$db = $YANA->connect('guestbook'); /* Remove 2nd row: */ $db->remove("table.2"); $db->write(); /** * will create the following SQL statement: * DELETE FROM table WHERE primary_key = "2" LIMIT 1; */ /* remove whole table: */ for ($i=0; $i < $db->length($table); $i++) { $db->remove("table.*"); } $db->write(); /** * will create the following SQL statement: * DELETE FROM table WHERE primary_key = "2" LIMIT 1; */ ?>
<?php global $YANA;
$db = $YANA->connect('guestbook'); if ($db->length('table') === 0) { print "The table is empty."; } else { print "The table contains ".$db->length('table')." rows."; } ?>
<?php global $YANA;
$db = $YANA->connect('guestbook'); /* check connection: */ if ($db->exists() === true) { print "Database connection available."; } else if ($db->exists() === false) { print "Database connection NOT available"; } /* check if table exists: */ if ($db->exists('table') === true) { print "Table exists."; } else if ($db->exists('table') === false) { print "No such table"; } /* check if row exists: */ if ($db->exists("table.2") === true) { print "The row '2' exists."; } else if ($db->exists("table.2") === false) { print "No row '2' in table."; } /* check if cell exists and is not null: */ if ($db->exists("table.2.field") === true) { print "There is a cell 'field' in row '2' which has a value."; } else if ($db->exists("table.2.field") === false) { print "The cell does not exist or is NULL."; } /* Check if there is at least one field that is not NULL: */ if ($db->exists("table.*.field") === true) { print "There is a value in column 'field'."; } else if ($db->exists("table.*.field") === false) { print "The column 'field' does not exist or contains no values."; } ?>
Manual providing of installation routines for the Yana Framework is not necessary.
The Yana Framework has a generic installation routine for databases, which you will find in the administrator's menu, at the "database setup". Using this menu a user can install all tables or synchronize contents between the DBMS and the flat-file database.
Figure: Opening a connection and installing databases
The Framework generates the necessary SQL instructions automatically from the structure file of your database. The following source code shows, how you can see the code generated by the Framework.
<?php $db = $YANA->connect('guestbook'); $dbe = new DbExtractor($db); // Generates "Create Table ..."-statements for MySQL $sql = $dbe->createMySQL(); // there are more functions for other DBMS $sql = $dbe->createPostgreSQL(); $sql = $dbe->createMSSQL(); $sql = $dbe->createMSAccess(); $sql = $dbe->createDB2(); $sql = $dbe->createOracleDB(); // show result print implode("\n", $sql); ?>
If the generated code does not correspond to your expectations, you can replace it by your own SQL file. Please copy your files to the directory "config/db/.install/{DBMS}", whereby you select the subdirectory, which corresponds to the desired DBMS instead of {DBMS}. The file "config/db/.install/readme.txt", contains a list of the supported DBMS and the names for the intended subdirectories to be used. They don't have to provide own files for all supported DBMS. If a required file does not exist, the Framework (like before) will produce the necessary SQL instructions itself automatically.
For further details see the API documentation of class: "DbExtractor".
<?php global $YANA; $db = $YANA->connect('guestbook'); $db->importSQL('data.sql'); ?>
<?php global $YANA; $db = $YANA->connect('guestbook'); $csv = $db->toString('table'); file_put_contents("table.csv", $csv); ?>
<?php global $YANA; $db = $YANA->connect('guestbook'); $db->exportStructure("guestbook.config"); ?>
Structure files are stored in the XML-like SML-format (see also the article: "The SML file format"). The following is an informal rough overview on the different elements.
The following informal text explains the structure *
DATABASE { use_strict (true | false) readonly? (true | false) description? #string include? (#string | #array) TABLES { readonly? (true | false) description? #string primary_key #string profile_key? #string foreign_keys? #array CONSTRAINT? { ... } TRIGGER? { ... } CONTENT { readonly? (true | false) description? #string type #string length? #numeric precision? #numeric required? (true | false | AUTO) default? #string unique? (true | false) index? (true | false) CONSTRAINT? { ... } TRIGGER? { ... } /* for numeric data types only */ unsigned? (true | false) zerofill? (true | false) DISPLAY? { HIDDEN { new? (true | false) edit? (true | false) select? (true | false) search? (true | false) } READONLY { new? (true | false) edit? (true | false) } /* for type "array" only */ numeric (true | false) } /* for type "image" only */ width? #numeric height? #numeric ratio? (true | false) background? #array } } } CONSTRAINT { select? #string insert? #string update? #string delete? #string } TRIGGER { before_insert? #string before_update? #string before_delete? #string after_insert? #string after_update? #string after_delete? #string }
* Help: Syntactic elements of the file are printed blue. The possible values and / or data types, which these elements may use are listed behind the elements. Data types are indicated by a rhomb symbol (#). The following data types are used: #string = text, #numeric = number, #array = list type. If multiple values or data types are allowed, they are listed in parentheses, with values separated by a pipe symbol (|). A question mark (?) Indicates that the element is optional.
The following section describes the various elements in detail and provides examples.
Schema files have the extension "*.config" and will be stored in the directory "config/db/". A connection on the basis of a schema file is created by this PHP code: $YANA->connect ("name of the file without extension");
Note that Code templates are available for the editors "ConTEXT" and "PSPad", which may save you a lot of work when creating database schemata. If you use one of these two editors, install these templates first before proceeding.
The following listing shows a scheme with all the elements and all valid values. If there are several possible settings, then these various possibilities are listed, separated by a pipe '|'. Identifier, which can be freely chosen, are highlighted by bold text.
/* The field "USE_STRICT" indicates whether queries * are to be checked against the schema at runtime or not. */ <USE_STRICT>true|false</USE_STRICT> /* The field "READONLY" is optional. Default = false */ <READONLY>true|false</READONLY> /* The field "DESCRIPTION" is optional and may contain a description, * or the database's name */ <DESCRIPTION>Text</DESCRIPTION> /* The field "INCLUDE" has the same meaning as the PHP-command * It imports one or more database schemata in the current file. * * The file name is given without the file extension. * For example: <INCLUDE>user</INCLUDE> to import the database "user" * which is described in the file "config/db/user.config". */ <INCLUDE>Database schema</INCLUDE> /* To select multiple files to import, use the following notation: */ <INCLUDE> <0>1st schema</0> <1>2nd schema</1> <2>3rd schema</2> </INCLUDE> /* Constraints are boolean expressions in PHP notation. * They can be linked to a specific SQL action. * If the expression evaluates to "false", the query will not be send * and an entry will be written to the logs. Otherwise the action will be carried out * without intervention. * * In Constraints you can't call any functions, with one * exception: preg_match(); * In addition, you have access to the following constants: * $VALUE = (for INSERT, UPDATE only) the inserted value * $PERMISSION = the security level of the user who triggered the action * $OPERATION = SQL-command that is currently being executed (SELECT, INSERT, ...) * $TABLE = name of target table (in most cases this is the current table) * $FIELD = name of destination column (if any) * $ID = id of current profile (since version 2.8.9) * * Examples: * <SELECT>true</SELECT> * <UPDATE>false</UPDATE> * <UPDATE>$VALUE > 0 && $VALUE < 500</UPDATE> * <INSERT>$PERMISSION > 50</INSERT> * <INSERT>preg_match('/^[\w\d-_]*$/i', $VALUE)</INSERT> * * Constraints are available in YANA since version 2.8 . */ <CONSTRAINT> <SELECT>PHP-Code</SELECT> <INSERT>PHP-Code</INSERT> <UPDATE>PHP-Code</UPDATE> <DELETE>PHP-Code</DELETE> </CONSTRAINT> /* Trigger are miniature programs in PHP syntax. * They are bound to a particular SQL action and are automatically executed *before o r after the SQL statement is executed. * * THESE triggers are different from those triggers in a database. * 1) PHP can be used, * 2) will work with the same code regardless of the DBMS, but: * 3) no direct access to the database. * * These triggers are particularly suitable for logging and modify input data. * * In addition, you have access to the following constants: * $VALUE = (for INSERT, UPDATE only) the inserted value * $PERMISSION = the security level of the user who triggered the action * $OPERATION = SQL command (BEFORE_INSERT, AFTER_UPDATE, ...) * $TABLE = name of target table (in most cases this is the current table) * $FIELD = name of destination column (if any) * $ID = id of current profile (since version 2.8.9) * * Examples: * <BEFORE_INSERT>$VALUE = md5($VALUE);true</BEFORE_INSERT> * <AFTER_DELETE>if($VALUE){print 'Successfully deleted.';}else{print 'Error.';}</AFTER_DELETE> */ <TRIGGER> <BEFORE_INSERT>PHP-Code</BEFORE_INSERT> <BEFORE_UPDATE>PHP-Code</BEFORE_UPDATE> <BEFORE_DELETE>PHP-Code</BEFORE_DELETE> <AFTER_INSERT>PHP-Code</AFTER_INSERT> <AFTER_UPDATE>PHP-Code</AFTER_UPDATE> <AFTER_DELETE>PHP-Code</AFTER_DELETE> </TRIGGER> /* As you may have already suspected: the option READONLY=true * and the constraint UPDATE=false both have the same effect. */ /* Here is the definition of the tables. */ <TABLES> <Name of table> <READONLY>true|false</READONLY> <DESCRIPTION>Label of table</DESCRIPTION> <CONSTRAINT> <SELECT>PHP-Code</SELECT> <INSERT>PHP-Code</INSERT> <UPDATE>PHP-Code</UPDATE> <DELETE>PHP-Code</DELETE> </CONSTRAINT> <TRIGGER> <BEFORE_INSERT>PHP-Code</BEFORE_INSERT> <BEFORE_UPDATE>PHP-Code</BEFORE_UPDATE> <BEFORE_DELETE>PHP-Code</BEFORE_DELETE> <AFTER_INSERT>PHP-Code</AFTER_INSERT> <AFTER_UPDATE>PHP-Code</AFTER_UPDATE> <AFTER_DELETE>PHP-Code</AFTER_DELETE> </TRIGGER> /* The field "PRIMARY_KEY" holds the name of the column, which is the primary key. (See literature under the keyword: "primary key constraint") */ <PRIMARY_KEY>Name of column</PRIMARY_KEY> /* The field "PROFILE_KEY" is the name of a column (if any), which contains the profile key. The purpose: You can assign the records of a table to a profile. If you do so, a user sees only the records of the selected profile. Creating a "profile key constraint": 1) Add a column of the type "profile" to the table 2) set the property "required" for this column to "AUTO" 3) Set the table's property "profile_key " to the name of the column (This is a special case of a "compound primary key" - this is a "primary key constraint". A "special case" because the key is a virtual key. This means that the technical handling for the user is done completely transparent. You can remove this constraint afterwards and the primary key will remain valid. That also means that you may subsequently add or remove this constraint, without having to change the source code of your plug-in. Checking and resolving of profile keys is done on the level of the database layer. This also means, when looking at the security of your application it is practically impossible to circumvent a "profile key" constraint. This feature was introduced in version 2.9.) */ <PROFILE_KEY>Name of column</PROFILE_KEY> /* The field "FOREIGN_KEYS" may hold a list of foreign keys. (See literature under the keyword: "foreign key constraint") */ <FOREIGN_KEYS> <name of column>name of target table</name of column> <another column>another target table</another column> </FOREIGN_KEYS> /* Here is the definition of table columns. */ <CONTENT> <Name of column> <READONLY>true|false</READONLY> <CONSTRAINT> <SELECT>PHP-Code</SELECT> <UPDATE>PHP-Code</UPDATE> </CONSTRAINT> <TRIGGER> <BEFORE_UPDATE>PHP-Code</BEFORE_UPDATE> <AFTER_UPDATE>PHP-Code</AFTER_UPDATE> </TRIGGER> /* The field "DESCRIPTION" is a label for this column. * You can also use %TOKEN% that you define in your program, * or in a language file, to be able to provide the label in multiple * different languages. */ <DESCRIPTION>Label of column</DESCRIPTION> /* The primitive, scalar data types integer, float and string correspond to their * equivalent in PHP. In addition, other useful data types have been introduced. * * mail = checks automatically, whether the value is a valid email address. * ip = checks automatically, whether the value is a valid IP address. * text = for input from text area fields, provides additional tests * to protect against flooding. * select = enumeration data type, which elements are defined in the "DEFAULT", * see below. * array = can be used to store PHP arrays. When reading the value * from the database, they are automatically converted to an array. * image = is used to store graphics. Handles file uploads, * testing and conversion of the graphic and automatically generates thumbnails. * file = this type is used to save files ("binary large objects"). * For reasons of better performance the files remain in the file system * after the upload. In order to save disk space, they will automatically be GZip-compressed. * The database will only store the file name. When you read the column * the name and location of the compressed file will be returned as a result. */ <TYPE>integer|float|string|text|url|ip|mail|time|select|array</TYPE> <LENGTH>positive integer</LENGTH> /* For data type "float" you may use "PRECISION" to determine the number of decimal places. */ <PRECISION>positive integer</PRECISION> /* The field "REQUIRED" selects, whether a field is NULLABLE or not. */ <REQUIRED>true|false</REQUIRED> /* The field "DEFAULT" sets a value that is automatically used when, * a data set is created and no other value is given. */ <DEFAULT>a Default value</DEFAULT> /* The field "UNIQUE" is either "true" or "false", where "false" * is the default value. If UNIQUE=true each value in this column may not occur more than * 1 time. This means, each value is unique ( thus the name). * Use this setting to define more keys in addition to the * key primary key. */ <UNIQUE>true|false</UNIQUE> /* The field "INDEX" is either "true" or "false", where "false" * is the default value. If INDEX=true a sorted list of all values of this column * are stored, what may increase the speed of sorting and searching for values * in the column. */ <INDEX>true|false</INDEX> /* * Instructions GUI and SDK can be filled in the field "DISPLAY". * There are two settings: "HIDDEN" and "READONLY". * Where "READONLY" means that this column is not displayed for editing. * As the name implies, "HIDDEN" means the columns is not to be displayed * at all. * There are separate settings for each of the queries: "NEW", "EDIT", "VIEW" and "SEARCH". * The properties can be set globally or individually for each action. */ /* First, the variant with global settings */ <DISPLAY> <HIDDEN>true|false</HIDDEN> <READONLY>true|false</READONLY> </DISPLAY> /* Nun die Variante mit lokalen Einstellungen für jede Option */ <DISPLAY> <HIDDEN> <NEW>true|false</NEW> <EDIT>true|false</EDIT> <SELECT>true|false</SELECT> <SEARCH>true|false</SEARCH> </HIDDEN> <READONLY> <NEW>true|false</NEW> <EDIT>true|false</EDIT> </READONLY> </DISPLAY> /* * For type "array" the property "display" may also carry the flag "NUMERIC" * to express that this is a numeric array. * This will hide the array keys in the graphical user interface of the program * and assign numeric keys to new entries automatically. * */ <DISPLAY> <NUMERIC>true|false</NUMERIC> </DISPLAY> </Name of column> /* * For the data types integer, ip and timethe field "REQUIRED" may have the value "AUTO" * to be set. This means that the value is automatically generated. * For time = current date as the Unix Time-Stamp * For ip = the IP of the visitor * For integer = auto-increment or the value of a sequence */ <name of column> <TYPE>integer|ip|time</TYPE> <REQUIRED>AUTO</REQUIRED> </name of column> /* * For the data type select a enumerated list of valid values can be given. * * The semantics can be relatively easy to remembered: * + The GUI displays columns of type "select" in forms as select-fields. * + The presentation in the scheme also recalls a select field in HTML. */ <name of column> <TYPE>select</TYPE> <DEFAULT> <default value>label</default value> <option 1>label 1</option 1> <option 2>label 2</option 2> </DEFAULT> </name of column> /* * For the data type image the maximum file size, width and height of the graphic * can be specified. If the image is too small or too large, it will automatically * be resized. The value "ratio" is to indicate, whether * the ratio of width to height is to sustain (true) or not (false). * If "ratio" is set to "true", resizing the image might cause a border around the image. * Therefore, a background color can be specified. * This background color fills the unused area around the graphics. * * The form generator can (if it is used) automatically create an upload field * to transfer graphic files to the server. * For each upload, a thumbnail of size 75x75px is created automatically. * The form generator automatically displays the thumbnail when calling the record * and a link to show the full graphic. * * For reasons of performance graphics are not stored as "blobs" in the database, * but in a non-public directory in the file system. * The database stores only the path to the graphic file. * When deleting the record (using the database API of the framework), * the graphic and thumbnail associated with the data set are deleted as well. */ <name of column> <TYPE>image</TYPE> <LENGTH>max. upload size in byte</LENGTH> <WIDTH>width in pixel</WIDTH> <HEIGHT>height in pixel</HEIGHT> <RATIO>true|false</RATIO> <BACKGROUND> <0>number 0-255 (red)</0> <1>number 0-255 (green)</1> <2>number 0-255 (blue)</2> </BACKGROUND> </name of column> /* * For numeric data types integer and float two * additional properties may be defined. * * unsigned: the number is stored unsigned * zerofill: when displaying the number free places are filled by '0' * * Note: The property "zerofill" works correctly only if the property * "length" has a fixed value. */ <name of column> <TYPE>integer|float</TYPE> <UNSIGNED>true|false</UNSIGNED> <ZEROFILL>true|false</ZEROFILL> <LENGTH>positive integer</LENGTH> </name of column> </CONTENT> </name of table> /* Further tables may follow here. */ </TABLES>
Now see this very simple example:
/* The head is usually always the same: */ <USE_STRICT>true</USE_STRICT> <READONLY>false</READONLY> /* This is followed by the definition of the tables in the database */ <TABLES> /* The table 'foo' */ <foo> /* Each table must have a primary key */ <PRIMARY_KEY>foo_id</PRIMARY_KEY> /* Now the columns */ <CONTENT> /* First, the primary key */ <foo_id> <TYPE>integer</TYPE> <LENGTH>8</LENGTH> /* REQUIRED=auto generates an "autoincrement" column */ <REQUIRED>auto</REQUIRED> /* HIDDEN=true makes the field invisible to visitors */ <DISPLAY> <HIDDEN>true</HIDDEN> </DISPLAY> </foo_id> /* Now the remaining fields */ <foo_title> <TYPE>string</TYPE> <LENGTH>128</LENGTH> <REQUIRED>true</REQUIRED> <DESCRIPTION>Title</DESCRIPTION> </foo_title> <foo_text> <TYPE>text</TYPE> <REQUIRED>false</REQUIRED> <DESCRIPTION>Text</DESCRIPTION> </foo_text> </CONTENT> </foo> /* a second table */ <bar> <PRIMARY_KEY>bar_id</PRIMARY_KEY> <CONTENT> <bar_id> <TYPE>string</TYPE> <LENGTH>32</LENGTH> <REQUIRED>AUTO</REQUIRED> <DISPLAY> <HIDDEN>true</HIDDEN> </DISPLAY> </bar_id> <bar_value> <TYPE>string</TYPE> <LENGTH>128</LENGTH> <REQUIRED>true</REQUIRED> <DESCRIPTION>Value</DESCRIPTION> </bar_value> <bar_time> <TYPE>time</TYPE> <REQUIRED>auto</REQUIRED> <DESCRIPTION>Time</DESCRIPTION> </bar_time> </CONTENT> </bar> </TABLES>
The following is an example of a more complex database. Portrayed is the data structure of the guestbook application:
<USE_STRICT>true</USE_STRICT> <READONLY>false</READONLY> <TABLES> <guestbook> <PRIMARY_KEY>guestbook_id</PRIMARY_KEY> <CONTENT> <guestbook_id> <TYPE>integer</TYPE> <LENGTH>5</LENGTH> <DESCRIPTION>Id (PK)</DESCRIPTION> <DISPLAY> <HIDDEN>true</HIDDEN> </DISPLAY> </guestbook_id> <profile_id> <TYPE>string</TYPE> <LENGTH>128</LENGTH> <REQUIRED>AUTO</REQUIRED> <DESCRIPTION>Id (FK)</DESCRIPTION> <DISPLAY> <HIDDEN>true</HIDDEN> </DISPLAY> </profile_id> <guestbook_ip> <TYPE>ip</TYPE> <LENGTH>15</LENGTH> <REQUIRED>AUTO</REQUIRED> <DISPLAY> <HIDDEN>true</HIDDEN> </DISPLAY> </guestbook_ip> <guestbook_name> <TYPE>string</TYPE> <LENGTH>128</LENGTH> <REQUIRED>true</REQUIRED> <DESCRIPTION>Name</DESCRIPTION> </guestbook_name> <guestbook_message> <TYPE>text</TYPE> <LENGTH>3000</LENGTH> <REQUIRED>true</REQUIRED> <DESCRIPTION>Text</DESCRIPTION> </guestbook_message> <guestbook_mail> <TYPE>mail</TYPE> <LENGTH>255</LENGTH> <DESCRIPTION>Mail</DESCRIPTION> </guestbook_mail> <guestbook_homepage> <TYPE>string</TYPE> <LENGTH>512</LENGTH> <DESCRIPTION>Homepage</DESCRIPTION> </guestbook_homepage> <guestbook_messenger> <DESCRIPTION>Messenger</DESCRIPTION> <TYPE>string</TYPE> <LENGTH>255</LENGTH> </guestbook_messenger> <guestbook_msgtyp> <DESCRIPTION>Typ</DESCRIPTION> <TYPE>select</TYPE> <LENGTH>5</LENGTH> <DEFAULT> <icq>ICQ</icq> <aol>AOL</aol> <yahoo>Yahoo!</yahoo> <msn>MSN</msn> </DEFAULT> </guestbook_msgtyp> <guestbook_opinion> <TYPE>select</TYPE> <LENGTH>1</LENGTH> <CONSTRAINT> <INSERT>$VALUE >= 0 && $VALUE <= 5</INSERT> <UPDATE>$VALUE >= 0 && $VALUE <= 5</UPDATE> </CONSTRAINT> <DEFAULT> <0>unentschlossen</0> <1>sehr gut</1> <2>gut</2> <3>befriedigend</3> <4>ausreichend</4> <5>ungenügend</5> </DEFAULT> <DESCRIPTION>Meinung</DESCRIPTION> </guestbook_opinion> <guestbook_date> <TYPE>time</TYPE> <REQUIRED>AUTO</REQUIRED> <DESCRIPTION>Datum/Zeit</DESCRIPTION> <DISPLAY> <HIDDEN> <NEW>true</NEW> </HIDDEN> </DISPLAY> </guestbook_date> <guestbook_comment> <TYPE>text</TYPE> <LENGTH>1024</LENGTH> <REQUIRED>false</REQUIRED> <DESCRIPTION>Kommentar</DESCRIPTION> <DISPLAY> <READONLY> <EDIT>true</EDIT> </READONLY> </DISPLAY> </guestbook_comment> <guestbook_is_registered> <TYPE>integer</TYPE> <LENGTH>1</LENGTH> <REQUIRED>AUTO</REQUIRED> <DEFAULT>0</DEFAULT> <DISPLAY> <HIDDEN>true</HIDDEN> </DISPLAY> </guestbook_is_registered> </CONTENT> </guestbook> </TABLES>
The following code creates two tables "foo" and "bar". The table "bar" contains a foreign-key reference to table "foo".
<TABLES>
<foo>
Primary key constraint:
<PRIMARY_KEY>foo_id</PRIMARY_KEY>
<CONTENT>
<foo_id>
<TYPE>integer</TYPE>
<DESCRIPTION>Primary key of table foo</DESCRIPTION>
<REQUIRED>AUTO</REQUIRED>
</foo_id>
<foo_name>
<TYPE>string</TYPE>
<DESCRIPTION>A column containing a name or title</DESCRIPTION>
</foo_name>
</CONTENT>
</foo>
<bar>
Primary key constraint:
<PRIMARY_KEY>bar_id</PRIMARY_KEY>
Foreign key constraint:
<FOREIGN_KEYS>
foo_id = column name
foo = table name
<foo_id>foo</foo_id>
</FOREIGN_KEYS>
<CONTENT>
<bar_id>
<TYPE>integer</TYPE>
<REQUIRED>AUTO</REQUIRED>
<DESCRIPTION>Primary key of table bar</DESCRIPTION>
</bar_id>
<foo_id>
<TYPE>select</TYPE>
<REQUIRED>true</REQUIRED>
<DESCRIPTION>Foreign-key on table foo, presented as select-box</DESCRIPTION>
<DEFAULT>
foo_id = column, which is to be stored in the database
foo_name = column, visible in the browser
<foo_id>foo_name</foo_id>
</DEFAULT>
</foo_id>
</CONTENT>
</bar>
</TABLES>
The function "create" (see section on templates) calls the form generator. The form generator is capable of using the information in the database schema to generate forms to view, search, create, edit, and delete records in the database. Necessary queries are carried ot automatically. In database schemata, the tag "DISPLAY" is used to control the behavior of the form generator. This tag allows to hide individual columns of a table, depending on the type of the current form.
The form generator shows table columns depending on the data type in different ways. The following is a comparison of data types and screenshots showing the respective representation in the GUI.
Type of column | Representation in the GUI | Description |
---|---|---|
integer, float, string | ![]() |
Strings and numbers are displayed as input fields when edited. If the column is editable, the content is displayed as text. |
boolean | ![]() |
Entries of type "boolean" use a checkbox. When viewing the column, a graphic indicates the state of the field. |
text | ![]() |
Multi-line texts use textarea fields for editing. If the column is not editable, it's contents are shown as text. If a text is too long, scrollbars are shown (CSS: "overflow: auto"). |
url, ip | ![]() |
The data type "ip" offers the possibility to automatically store the IP address of the visitor. IP addresses are checked for syntactic validity. As a rule, columns of this type should not be editable. If they are, they use an input box for editing. The data type "url" is displayed just like a column of type "string", with the slight difference that every input is checked to be a syntactically correct URL. |
![]() |
This data type also uses an input box for editing. Inputs are checked for syntactical validity. Values of type "mail" are encoded when shown in the browser, to make data theft more difficult. This applies to all displayed mail addresses. The implementation is done automatically in the presentation layer of the framework, manual intervention is therefore unnecessary. | |
select | ![]() |
The data type "select" is an enumeration type. When editing such column, a select box is shown. The contents of the box may be predefined in the database schema (using the property "Default"). If the field is a foreign key (i.e., if this column has a foreign key constraint), the menu will automatically be filled using the entries of the linked table. You may also specify the columns the labels and values are taken from. |
set | ![]() |
The data type "set" is another variant of an enumeration type, which permits the use of multiple values (unlike "select", which only allows a single value). When editing such column, a list of check boxes is presented. Values and labels of the boxes can set in the database schema (using the property "Default"). |
time | ![]() |
When editing columns of type "time", select boxes are shown to ease the input of dates. Entries of type "time" will be automatically saved as a time stamp and presented in the GUI as a date. The format of presentation can be chosen in the administration menu and adapt to the chosen language settings automatically. Using the framework, dates are automatically synchronized with the visitor's time zone and its respective locale settings. |
array | ![]() |
Entries of type "array" may be multidimensional arrays. When editing these, contents are presented as pairs of keys and values. By clicking "remove" a value is deleted, click on "add new entry" to append a new value. Values are presented as multidimensional foldable tree menus. When touching a key with the mouse, the list of all entries associated with this key, is shown. |
array (numeric) | ![]() |
Numerical lists are defined using the property "display.numeric". They differ from "normal" arrays because they present no fields to input individual keys. Instead keys are chosen automatically when creating a new record. Values are presented as list elements. |
image | ![]() |
Columns of the data type "image" are presented as a thumbnail image plus an upload field to insert or replace the stored graphic. When clicking the thumbnail the complete graphic will be shown in a new window. The graphics file is automatically checked and converted during upload. You may specify settings in the database schema to control this conversion, such as the desired image size. |
file | ![]() |
The data type "file" is used to save files ("binary large objects"). When editing, it will present an upload field to upload a new file, and a button to download the current one. The files remain in the file system (not in the database) for better performance. In order to save disk space, they will automatically be GZip-compressed. This compression also ensures that files stores on the server are not executable and a potential attacker can't misuse such upload fields to store malicious code on the server. When you download the file will be unpacked automatically, so the user no disadvantages from the experiences compression and decompression not installed. To have a faster download, the file is automatically send as compressed data stream, if the user's browser supports this feature (most current browser do). The browser unpacks the file independently. A manual intervention is not necessary. The framework automatically takes care of this feature. |
Comparison of data types and their representation by the form generator
The downloading of files or opening of graphics for columns of data type "image" or "file", is implemented in the function "download_file", which is located in the plug-in "default_library". This usually happens automatically.
For security reasons, access to this action is by default limited to users of the user group "admin". This is the user group with the highest level of security. If you want to permit access to these data to users with a lower level of security as well, you need to reduce security restrictions for this action.
To do this, edit the file "plugins/default_library.config" in a text editor of your choice. Look for the following section.
<DOWNLOAD_FILE> <TYPE>primary</TYPE> <MODE>1</MODE> <TEMPLATE>NULL</TEMPLATE> <PERMISSION>100</PERMISSION> </DOWNLOAD_FILE>
The security level is represented as a number between 0 and 100, with 100 representing the group "admin" and 0 the group "guest". Change the value "100" to a number that seems reasonable to you.
Some proposals are in the following table:
User group | Security level |
---|---|
guest | 0 |
registered user (registered) | 1 |
moderator (mod) | 30 |
owner | 75 |
administrator (admin) | 100 |
Comparison of user groups and levels of security
In this example, the value is set to "1".
<DOWNLOAD_FILE> <TYPE>primary</TYPE> <MODE>1</MODE> <TEMPLATE>NULL</TEMPLATE> <PERMISSION>1</PERMISSION> </DOWNLOAD_FILE>
Save and close the file.
In order for the changes to take effect, you have to go to the administration menu of the Yana framework and refresh the plug-in cache.
After clicking the button, the settings take effect immediately.
The function dirlist() returns a sorted list of files and directories for a directory . The compulsory directory entries "." and ".." are not listed. Files whose filenames begin with the character "." will also not be listed. This applies in particular file names such as ".htaccess" or ".password". The file names contain no paths. Directory names do not contain a final dash "/".
The optional parameter $filter can be used to limit the results to certain file extensions. Wildcards are not permitted. The file filter can only contain alphanumeric characters, as well as the characters '.', ' -' and ' _'. Other characters are removed automatically.
list all items in a directory: <?php echo implode("<br>", dirlist("foo/")); ?> Output: foo1.foo2 foo3.foo2 foo4.foo5 foo6.foo7 list all entries with a certain file extension: <?php echo implode("<br>", dirlist('foo/', '*.foo2')); ?> Output: foo1.foo2 foo3.foo2 ... with multiple file extensions: <?php echo implode("<br>", dirlist('foo/', '*.foo2|*.foo5')); ?> Output: foo1.foo2 foo3.foo2 foo4.foo5 using OO-style notation: <?php $dirStream = new DirStream("foo/"); $dirStream->read(); $dirlist = $dirStream->dirlist('*.foo2|*.foo5'); echo implode("<br>", $dirlist); /* Note: setting the file filter has a permanent effect. */ $dirStream->dirlist('*.foo2|*.foo5'); echo $dirStream->length(); /* Output: 2 */ echo count($dirStream->get()); /* Output: 2 */ /* A new call to dirlist() resets the file filter */ $dirStream->dirlist(""); echo $dirStream->length(); /* Output: 4 */ ?>
The functions $dirStream->create($mode) and $dirStream->delete() are responsible for creating and deleting directories. The optional parameter $mode can be used to set permissions of directories for LINUX / UNIX operating systems. The default value is 777.
Examples of valid values for $mode are listed in the following table. Where "r" is "readable", "w" is "writable", and "x" is "executable.
Owner | Group | Other users | Value |
---|---|---|---|
rwx |
rwx |
rwx |
777 |
rw- |
rw- |
--- |
660 |
rw- |
rw- |
rw- |
666 |
rwx |
r-x |
r-x |
755 |
rwx |
r-x |
--- |
750 |
rwx |
--- |
--- |
700 |
Examples of valid values (corresponding to Unix: CHMOD)
<?php $dirStream = new DirStream("foo/"); $dirStream->read(); /* create directory "foo/" */ $dirStream->create(); /* directory "foo/" with access restrictions set to 660 */ $dirStream->create(660); /* get number of files in a directory */ print "Directory 'foo/' contains ".$dirStream->length()." files."; /* delete directory "foo/" */ $dirStream->delete(); /* delete directory "foo/" including all files and subdirectories */ $dirStream->delete(true); /* check if directory exists */ $test = $dirStream->exists(); if ($test === true) { print "directory exists"; } else if ($test === false) { print "directory does NOT exist"; } ?>
The function SML::getFile() reads the specified configuration file in SML format and returns the contents as a PHP variable.
<?php /* read contents from file and return result as an array */ $array = SML::getFile("foo.config"); /* transform keys to capital letters */ $array = SML::getFile("foo.config", CASE_UPPER); /* transform keys to in small letters */ $array = SML::getFile("foo.config", CASE_LOWER); /* leave keys untouched (default) */ $array = SML::getFile("foo.config", CASE_MIXED); /* ... or: */ $array = SML::getFile(file("foo.config")); /* ... or: */ $array = SML::decode(file_get_contents("foo.config")); /* create and decode an SML-encoded string */ $input_array = array(1,2,3,array(4,5),'a'=>6,'B'=>7); $output_array = SML::decode(SML::encode($input_array)); $input_array == $output_array; // true /* OO-style, mixed key case */ $configFile = new ConfigFile("foo.config"); $configFile->read(); $array = $configFile->get(); /* OO-style capitalized key names */ $smlFile = new SML("foo.config"); $smlFile->read(); $array = $smlFile->get(); ?> Navigating within a SML file: Given the file "foo.config" with the following contents: <ROOT> <FOO1> <FOO2>text</FOO2> <FOO3> <0>1</0> <1>foo</1> </FOO3> </FOO1> </ROOT> <?php $mixed = $smlFile->get("ROOT.FOO1"); print_r($mixed); ?> This query returns the subtree ROOT.FOO1 with FOO1 as the root element. Output: array( "FOO2" => "text", "FOO3" => array ( 0 => 1, 1 => "foo" ) )
The function SML::encode() returns a representation of a specified variable as SML code. The input must be either a variable with a scalar value or an array.
Pleas note: the function "SML: encode" does not detect infinite recursion. Therefore data fields need to be free of recursion. Otherwise, the compiler will present an error message.
Save the contents of an array to "foo.config": <?php $string = SML::encode($array, "ROOT"); file_put_contents("foo.sml", $string); ?> Replace "ROOT" with the name of the root element! Benutzen von Großbuchstaben, Kleinbuchstaben und gemischter Schreibweise analog zu SML::decode(): <?php $string = SML::encode($array, "ROOT", CASE_UPPER); // groß $string = SML::encode($array, "ROOT", CASE_LOWER); // klein $string = SML::encode($array, "ROOT", CASE_MIXED); // gemischt (default) ?> objektorientierte Variante, gemischte Schreibweise: <?php $configFile = new SML("foo.sml"); $configFile->read(); $configFile->create(); $configFile->insert($string); $configFile->write(); ?> objektorientierte Variante, Schlüssel in Großbuchstaben: <?php $smlFile = new SML("foo.sml", CASE_UPPER); $smlFile->read(); $smlFile->create(); $smlFile->insert($string); $smlFile->write(); ?>
To copy a file, all YANA classes that represent files, implement a function "copy". When copying the file you may also set access restrictions. If the directory, in which the file is to be copied, does not exist, it can be created automatically. The latter is optional.
The following are some examples.
copy a file: <?php $foo = new SecureFileStream("foo.txt"); $foo->copy("bar.txt"); $foo->copy("bar.txt"); // error: file already exists ?> copy a file and overwrite existing files: <?php $foo->copy("bar.txt", true); $foo->copy("bar/foo2.txt"); // error: directory 'bar' does not exist ?> copy a file and create missing files automatically: <?php $foo->copy("bar/foo2.txt", false, true); /* The directory 'bar/' has been created and file 'foo2.txt' has been stored in it. Access restrictions of this directory have been set to 766 by default. */ /* deleting the directory */ $dir = new DirStream("bar"); $dir->delete(true); ?> copy a file and set access restrictions of the copied file to 655: <?php $foo->copy("bar/foo2.txt", false, false, 655); /* directory 'bar/' has been created again. The access restrictions for the directory have been set to 655 - as for the file. */ ?>
The framework provides the ability to create persistent counter variables, whose value is automatically saved.
There are two types of counters variables: those with IP-check, which only change if the user with the current IP has not already been to the page within the last 3 hours. And those without IP check, which always change when called.
The following are some examples.
<?php /* Create counter with IP-check */ $counter = new Counter("my_stats", YANA_COUNTER_USE_IP); /* ... or: */ $counter = new Counter("my_stats"); /* Create counter without IP-check */ $counter = new Counter("my_stats", YANA_COUNTER_IGNORE_IP); /* Increment counter "test1" */ $counter->count("test1"); /* Increment counter "test2", and save a description */ $counter->count("test2", "score"); /* Increment counter "test1" by 3 */ $counter->count("test1", "score", 3); /* Decrement counter "test1" by 2 */ $counter->count("test1", "score", -2); ?>
<?php $counter = new Counter("my_stats"); /* Get value of counter "test1" */ $int = $counter->getCount("test1"); /* Get description of counter "test1" */ $string = $counter->getInfo("test1"); print $string.":\t".$int; /* Output score: 2 */ /* get all counters */ $array = $counter->getCount("*"); foreach ($array as $a => $b) { print $a.":\t".$b; } ?>
<!-- Insert this in your template file: --> <img src=[%"action=graphic_counter&target=0"|href%]>
The parameter "target" selects the background image. Valid values are the numbers 0-6.
Value of parameter "target" | Presentation |
---|---|
0 | ![]() |
1 | ![]() |
2 | ![]() |
3 | ![]() |
4 | ![]() |
5 | ![]() |
6 | ![]() |
Version information: The functions send_mail and form_mail, and the class form_mailer were renamed to sendMail, formMail and formMailer in version 2.8 to comply with the naming convention of the framework. Since version 2.8 the function sendMail is a static function of class mailer. Since version 2.8 the function formMail is a static function of class mailer.
The framework offers the function Mailer::mail() to send mail. This function implements a series of security measures, for protection against various types of header-injection attacks.
The function "Mailer::mail" is a variant of the native PHP function "mail", that has been secured against misuse and "header-injection" attacks. Unlike "mail", all input data is checked and header information is checked and restricted.
It returns "true", when the mail was sent and "false" otherwise. However, the value "true" does not mean the mail successfully arrived at its destination. It just means that the input was syntactically correct.
The function adds to the header of the mail the additional entries "x-yana-php-header-protection" and "x-yana-php-spam-protection".
If you receive an e-mail, which was sent through YANA, see the headers of the e-mail to check whether the framework discovered irregularities in the text of the message.
For security reasons, the following restrictions apply.
Recipient: $recipient
The parameter must be a valid email address, as described with this regular expression (Perl syntax)
/^[äöüß\w\d-_\.]{1,}\@[äöüß\w\d-_\.]{2,}\.[\w\d-_\.]{2,}$/i
Subject: $subject
All special characters except "()äÄüÜöÖß[]", all tags and all line breaks are removed without notice. If the subject has more than 128 characters it is truncated.
Message text: ($text)
All '@'-symbols are replaced by "[at]". Text messages are wrapped at 70 characters. For HTML messages some potentially dangerous tags are removed (blacklist approach).
Header: $header
This parameter is an associative data field. It may contain any X-header entries, as well as some noncritical header information (whitelist approach).
Parameter | Type | Default | Description |
---|---|---|---|
from | - | valid mail address | |
return-path | - | valid mail address | |
cc | mixed | - | Either a valid mail address or a numeric data field with multiple valid mail addresses. A copy will be send to all these addresses. Unlike "bcc" the list of recipients is visible to all recipients. |
content-type | string | text/plain; charset="UTF-8" |
Determines the MIME type of the message. Only MIME-type and charset are valid here. Other values are ignored. |
mime-version | float | 1.0 | Must be in accordance with the following regular expression (in Perl syntax): /^\d\.\d$/ |
content-transfer-encoding | string | - | Must be in accordance with the following regular expression (in Perl syntax): /^\d{,2}bit$/i |
Valid values for the parameter $header of the function Mailer::mail()
The use of "BCC" is not permitted for security reasons.
<?php $recipient = "myMail@domain.tld"; $subject = "Notice"; $mailer = new Mailer("skins/mytheme/example.mail"); $mailer->subject = $subject; $mailer->sender = $ARGS["mail"]; $mailer->insert("NAME", $ARGS["name"]); $mailer->insert("NACHRICHT", $ARGS["message"]); $mailer->insert("IP", $_SERVER["REMOTE_ADDR"]); $test = $mailer->send($recipient); if ($test === true) { print "Success"; } else if ($test === false) { print "Error"; } ?>
<?php $recipient = "myMail@domain.tld"; $subject = "Notice"; $mailer = new Mailer("skins/mytheme/example.mail"); $mailer->subject = $subject; $mailer->sender = $ARGS["mail"]; $mailer->insert("NAME", $ARGS["name"]); $mailer->insert("NACHRICHT", $ARGS["message"]); $mailer->insert("IP", $_SERVER["REMOTE_ADDR"]); $test = $mailer->send($recipient); if ($test === true) { print "Success"; } else if ($test === false) { print "Error"; } ?>A mail could be sent as follows:
Note: before sending the e-mail, the function $mailer->send() checks all input data automatically for attempts to inject header data and cleans all input where necessary. However, it does no harm to also check the input in your own script before calling the function.
<?php $formMailer = new FormMailer(); // Subject $formMailer->subject = "Notice"; // header and footer $formMailer->headline = "Contents of contact form:\n\n" $formMailer->footline = "\n\nYANA form mailer at ".$_SERVER['SERVER_NAME']; // Form contents $formMailer->content = $_POST; $test = $formMailer->send("myMail@domain.tld"); if ($test === true) { print "Success"; } else if ($test === false) { print "Error"; } ?>A mail could be sent as follows:
To do this, YANA offers a function named untaintInput().
The function has the following parameters:
Parameter |
Type |
Default |
Description |
---|---|---|---|
value | mixed | - | data to be cleaned |
type | string | - | Data type In addition to the supported native types of PHP the following values are allowed:
|
length | integer | 0 | Maximum length of data |
escape | integer | 0 | see table |
List of parameters for untaintInput
The parameter $escape can be any one of the following constants.
Identifier | Description |
---|---|
YANA_ESCAPE_NONE | No changes (Default) |
YANA_ESCAPE_SLASHED | Converts single and double quotation marks to their respective escape sequences in C-notation |
YANA_ESCAPE_TOKEN | replaces token by their HTML entities |
YANA_ESCAPE_CODED | replaces HTML symbols, such as Tags, by entities |
YANA_ESCAPE_LINEBREAK | converts all whitespace characters (particularly line breaks) into spaces |
YANA_ESCAPE_USERTEXT | for treatment of input from text area fields |
Valid values for parameter $escape, function untaintInput
For INPUT fields you should always call untaintInput() with the parameter YANA_ESCAPE_LINEBREAK. This will prevent an attacker from smuggling line breaks into the output, which might be a possible threat. For TEXTAREA fields you should use YANA_ESCAPE_USERTEXT. This prevents many forms of flooding, by constantly repeated texts (Copy'n'Paste Flooding), and will wrap oversized text strings, trim white space and thus will ensure, the layout of your page is not broken.
YANA writes all log entries in a table of the database. However, this is done only, when logging is enabled. You can disable this to reserve space. This feature is disabled by default.
Figure: options, program setup
Figure: activate logging
Figure: List of journal entries
<?php global $YANA; /* Write a text to the logs */ $log = new Log("My log-entry"); $YANA->report($log); /* Text and dump of data */ $dump = $some_data; $log = new Log("My Log-entry",$dump); $YANA->report($log); /* view the created log-entry */ print $log->getLog(); ?>
You may output a text message by calling $YANA->report(Report $report).
The parameter $report is an instance of one of the following classes. These classes have the same parent class "Report".
Class | Description |
---|---|
Log | for output to log-file |
Message | Success messages (screen) |
Warning | Warning |
Alert | Note |
Error | Error |
accepted types for parameter $report
The constructor function is called as follows:
new Log(string $text [, mixed $data]);
The following example writes a message in the log file, then generates a text for output on the screen.
$YANA->report( new Log("IO-ERROR: Unable to read file
$a") );
$YANA->report( new Error('NOT_READABLE', array('FILE' =>
$a) ) );
The parameter $text may either be an error code, or a text message. You should use error codes for screen output. For output in log files you should use English texts. The $data parameter is optional and may provide additional information. For example, the data set, which could not be saved, or the name of a file that could not be opened.
Valid error codes for parameter $text include (EXCERPT):
Code | Description | Text excerpt |
---|---|---|
200 | Success | Your changes have been saved. Thank You !!! |
500 | Generic error message | An error occured... |
403 | Error: Permission denied. | Password required. You are entering a protected area... |
INPUT_INVALID | Error: invalid input | A chosen parameter is not valid... |
404 | Error: File not found | Please check the URL and try again. |
ALLREADY_EXISTS | Error: Entry already exists | Unable to create an entry with the id "%ID%" because another entry with the same name already exists. |
FILE_NOT_CREATED | Error: IO failure | Unable to create file %FILE%. |
NOT_WRITEABLE | Error: IO failure | Unable to perform write operation on resource "%FILE%". Data could not be saved. |
NOT_READABLE | Error: IO failure | Unable to perform read operation on resource "%FILE%". Data could not be read. |
accepted values for parameter $text
Included token, such as% FILE% will be replaced in the following manner:
<?php $YANA->report( new Warning('FILE_NOT_CREATED', array('FILE' => $a)) ); ?>
The special thing about $ YANA->report() is, that it may simultaneously be used to send text messages to a log file and output messages on the screen. If the method is called several times, several text messages are printed.
<?php $YANA->report( new Warning('FILE_NOT_CREATED', array('FILE' => $a)) ); /* ... */ $YANA->report( new Alert('NOT_WRITEABLE', array('FILE' => $a)) ); /* ... */ $YANA->report( new Error('500') ); $YANA->report( new Log("Input/Output Error in File $a.", $lost_data) ); return false; ?>
The call to Yana->report only creates the text message, but does not exit the program. In order to exit the program, use the method Yana->exitTo. Note: Do NOT use the PHP methods exit () or die(). Otherwise, no output is produced.
The method Yana->exitTo( [string $action] ) can be used to interrupt the program and define a follow-up action to be executed. This means that the processing of the current action will be canceled and instead the script will be continued with the action $action.
The framework will send all text messages to the browser, and then call itself again. The parameter $action corresponds to the URL parameter "action".
If the parameter $action is not specified, then the front page (whatever it is) will be called instead.
You may also use the special action "null", if you just want to quit the program and NOT continue with another action.
Here is a simple example:
<?php // Creating some text messages $YANA->report( new Log('An entry for the logs') ); $YANA->report( new Alert('NOT_WRITEABLE', array('FILE' => $a)) ); // stop current program and continue with action 'index' $YANA->exitTo('index'); // create error message $YANA->report( new Error('Access denied!') ); // exit the program (do NOT continue with another action) $YANA->exitTo('null'); // exit the program and go to the front page $YANA->exitTo(); ?>
YANA provides a global memory, that can be read and modified by all plug-ins. All variables stored this area will be accessible in all skins and templates. Please note: the global storage area in YANA is something other than the global namespace of PHP.
Values stored here are presented as a data tree and are organized as multidimensional, associative arrays. This data may be accessed via keys, analogous to Smarty - or, if you like, similar to XPath.
For example:
<?php $array = $YANA->getVar("*"); // use the key "*" to get a copy of the whole array $int = $YANA->getVar("a"); // output: 2 $array = $YANA->getVar("b"); // output: array("c" => 3, 1 => 4) $int = $YANA->getVar("b.c"); // output: 3 /* Unlike to XML-files, numeric ids may be used: */ $int = $YANA->getVar("b.1"); // output: 4 ?>
The function $YANA->getVar() enables you to read values in the global storage area.
<?php global $YANA; /* get all values */ $array = $YANA->getVar("*"); /* get specific values */ $mixed = $YANA->getVar("FOO1.FOO2.FOO3"); /* alternative notation */ $mixed = $YANA->registry->getVar("FOO1.FOO2.FOO3"); ?>
The function $YANA->setVar() enables you to write values in the global storage area.
<?php global $YANA; /* register variable "MY_VAR" */ $bool = $YANA->setVar("MY_VAR", $new_value); $bool = $YANA->setVar("FOO1.FOO2.MY_VAR", $new_value); /* alternative notation */ $mixed = $YANA->registry->setVar("FOO1.FOO2.MY_VAR", $new_value); ?>
To do so use the function $YANA->unsetVar(). For example:
<?php global $YANA; /* unset var "MY_VAR" */ $bool = $YANA->unsetVar("MY_VAR"); $bool = $YANA->unsetVar("FOO1.FOO2.MY_VAR"); /* alternative notation */ $mixed = $YANA->registry->unsetVar("FOO1.FOO2.MY_VAR"); ?>
Use the function $YANA->setType() to do this. For example:
<?php global $YANA; /* set type of "MY_VAR" to string */ $bool = $YANA->setType("MY_VAR", "string"); $bool = $YANA->setType("FOO1.FOO2.MY_VAR", "string"); /* alternative notation */ $mixed = $YANA->registry->setType("FOO1.FOO2.MY_VAR", "string"); ?>
<?php global $YANA; $array = $YANA->getVar("INSTALLED_PLUGINS"); print_r($array); ?>Output:
Array ( [CONFIG] => 1 [DB_ADMIN] => 1 [DEFAULT_LIBRARY] => 1 [GUESTBOOK] => 1 [GUESTBOOK_ADMIN] => 1 [USER] => 1 )Checking whether a particular plug-in is installed:
<?php $bool = $YANA->getVar("INSTALLED_PLUGINS.MY_PLUGIN"); if ($bool) { print "Plug-in 'MY_PLUGIN' found."; } else { print "Plug-in 'MY_PLUGIN' not found."; } /* alternative notation */ $YANA->plugins->isInstalled('MY_PLUGIN'); ?>
The "Web site profile" is selected by using the URL-parameter "id". Each profile can have individual settings. These settings are also available via the global memory. If no profile is selected, the profile "default" will be used. In fact, as a programmer you don't need to care which one actually is selected. All you need to know is, that profile data contains settings like the background color of the page, preferred font family and other interesting data, which you can access when necessary.
<?php global $YANA; /* read current profile settings */ $array = $YANA->getVar("PROFILE"); print_r($array); ?>
Array ( >> Web site layout [BGCOLOR] => #F0F0F0 [PFONT] => Arial, Helvetica, sans-serif [BGIMAGE] => [LOGO] => [HSIZE] => [HCOLOR] => [HFONT] => [PSIZE] => [PCOLOR] => >> Selected language, skin and directory for emot-icons / smilies [LANGUAGE] => english.config [SKIN] => default.config [SMILEYDIR] => common_files/smilies/ >> Other options [USERMODE] => 1 [AUTO] => 1 [TIMEFORMAT] => 0 >> Logging [LOGGING] => 1 [LOG_LENGTH] => 50 >> Settings for plug-in "rss to html factory" [RSS] => Array ( [FILE] => plugins/rss/test.rss [MAX] => 5 ) >> Settings for plug-in 'guestbook' [GUESTBOOK] => Array ( [NOREPLY] => noReply@meineAdresse.tld [FLOODING] => 0 [ENTPERPAGE] => 5 [NOTIFICATION] => [SENDMAIL] => [MAIL] => myName@domain.tld [FILE] => [SPAMPROTECT] => 1 [PROFILE] => [USE_DB] => ) >> Settings for plug-in "search" [SEARCH] => Array ( [TARGET] => _self ) >> Settings for plug-in "user_admin" (user management) [USER] => Array ( [ALLOW_CREATE] => 0 ) ) <?php /* read the selected background color */ $string = $YANA->getVar("PROFILE.BGCOLOR"); print '<p style="background-color: '.$string.'">test</p>'; ?>
<?php global $YANA; /* read YANA default settings */ $array = $YANA->getVar("DEFAULT"); print_r($a); ?>Output:
Array ( >> Determines what action should be called automatically when the Argument "action" is empty or not set. Default is "sitemap" - but you may use something other, eg. "guestbook_read_read" to start the guestbook, or "search_start" for the search engine. This setting was introduced in version 2.8.0 [HOMEPAGE] => sitemap >> Determines whether text messages (errors, notes, etc.) are viewed in a separate window (true) or in the text of the current page (false). This setting was introduced in version 2.8.2 [MESSAGE] => false >> Default event configuration for events that are not defined [EVENT] => Array ( [TYPE] => default [MODE] => 0 [PERMISSION] => 0 [TEMPLATE] => index [INSERT] => ) >> Default interface configuration [INTERFACE] => Array ( [TEST] => Array ( [TYPE] => private [MODE] => 0 [PERMISSION] => 0 [TEMPLATE] => NULL ) ) >> Default skin configuration [SKIN] => Array ( [DIRECTORY] => default/ ) >> Default language configuration [LANGUAGE] => Array ( [DIRECTORY] => en/ ) >> Default for database connections [DATABASE] => Array ( [DSN] => Array ( [USE_ODBC] => [DBMS] => mysql [HOST] => localhost [PORT] => 0 [USERNAME] => root [PASSWORD] => [DATABASE] => yana ) >> Configuration of the database interface [OPTIONS] => Array ( [AUTOFREE] => 1 [PERSISTENT] => 1 [SSL] => ) >> List of DBMS, which may be addressed via the ODBC interface [REQUIRE_ODBC] => Array ( [0] => db2 [1] => access ) ) )
<?php global $YANA; /* User IP */ print $YANA->getVar("REMOTE_ADDR")."\n"; /* User name */ print $YANA->getVar("SESSION_USER_ID")."\n"; /* access restriction of users */ print $YANA->getVar("PERMISSION")."\n"; /* Session name */ print $YANA->getVar("SESSION_NAME")."\n"; /* Session id */ print $YANA->getVar("SESSION_ID")."\n"; ?> Output: 127.0.0.1 ADMINISTRATOR 100 YSID 480f97e69eae2311d3157cc3377a2d73
A CAPTCHA is a used for protection from Spam. Therefor a graphic is displayed containing a text, which the user must retype. While a human may solve this task easily, a Bot will most possibly fail. This way a large amount of junk messages may be avoided.
The Framework has a function for producing a CAPTCHA diagram in the png format (MIME type "image/png"), which contains a coincidentally produced code of numbers and letters. A parameter indicates the item number of this code in the current code table. The code table contains 10 entries and expires automatically within a period of 10 minutes through 3 hours after the call of the function. If the table has expired, a new table is created automatically.
The Yana PHP-Framework already implements such a function. You don't have to write it - just use it.
The CAPTCHA consists of two parts: a graphic with an input field, which is to be included in the template and a check-code, which is to be inserted in the source code of the plug-in and which returns true or false, if the input was correct or wrong.
See the following example:
[%captcha%]
Figure: Example of the representation in the Browser
<?php global $YANA; /** * to check input: * * The variable $form_data can be set to * $_POST, $_GET, or $ARGS. */ $bool = $YANA->handle("security_check_image", $form_data);
if ($bool) { print "Input correct."; } else { print "Input is false."; } ?>
Support for CAPTCHAs has been introduced in version 2.8.0. The example above works for Yana PHP-Framework version 2.9.3. In this version handling of CAPTCHAs was simplified.
For the older version 2.8.0 through 2.9.2 use the following example:
<input type="hidden" name="security_image_index" value="[%$SECURITY_IMAGE_INDEX%]">
<img src=[%"action=security_get_image&security_image_index=$SECURITY_IMAGE_INDEX"|url%]>
<input type="text" name="security_image">
Source code of the plug-in:
<?php global $YANA; /* to create the form: */ /* The variable SECURITY_IMAGE_INDEX needs to be set for the form. It is an integer of 1 through 9. */ $YANA->setVar("SECURITY_IMAGE_INDEX", rand(1,9)); /** * to check input: * * The variable $form_data can be set to * $_POST, $_GET, or $ARGS. */ $bool = $YANA->handle("security_check_image", $form_data);
if ($bool) { print "Input correct."; } else { print "Input is false."; } ?>
For even older versions (before version 2.8.0) this function has to be included by hand.
The interface of a plug-in is a stored as a file with the extension "*.config". You can find these files in the directory "plugins/".
/* see the interface description plugins/foo.config */ <INTERFACE> <name of action> <TYPE>primary|default|write|read|security|config</TYPE> /* Type: - "primary" for core processes of a main program - "security" for security functions, such as the testing of passwords - "config" for functions to edit configuration files - "write" for write access to the file system or database - "read" for read access to the file system or database - "default" is intended for developers who are undecided where to put the action */ <MODE>0|1</MODE> /* Mode: - 0 (default) normal operating mode - 1 start action in default configuration ("safe mode") (for safety critical tasks) */ <TEMPLATE>Id of template (e.g. INDEX)</TEMPLATE> <INSERT>Id of template (e.g. MY_TEMPLATE)</INSERT> /* Templates: Names of templates for the output - the file named in "INSERT" will be embedded in the "TEMPLATE"-file - "TEMPLATE" is a static "frame" around the content, default value is INDEX" - The name of the template is the name defined in the skin file. By comparison open, e.g. the file skins/default/default.config . - The special template "NULL" will prevent any output from being generated (Very convenient if you don't intend to output a HTML file but a file, e.g. a PNG graphics file) - The special template "MESSAGE" creates a text message. */ <PERMISSION>1</PERMISSION> /* Permission: Specifies which minimum permissions are required for a user, to be able to call this action. The value is between 0 and 100, where the value 0 = "no restriction". You can temporarily deactivate a function by setting the property "permission" to -1. In this case, nobody can trigger this action. */ <ONSUCCESS> <TEXT>Name of text message</TEXT> <GOTO>Name of action</GOTO> </ONSUCCESS> <ONERROR> <TEXT>Name of text message</TEXT> <GOTO>Name of action</GOTO> </ONERROR> /* OnSuccess / OnError: You may specify an action to automatically go to, when the current action was successful, or an error occurred. Additionally you can indicate a text message, which is to be shown. You find a list of the text messages in the file "languages/en/message.config". */ </name of action> /* an example */ <foo> <TYPE>write</TYPE> <MODE>0</MODE> <TEMPLATE>MESSAGE</TEMPLATE> <PERMISSION>75</PERMISSION> <ONSUCCESS> <GOTO>foo_read</GOTO> </ONSUCCESS> <ONERROR> <GOTO>foo_edit</GOTO> </ONERROR> <foo> </INTERFACE> the plug-in class plugins/foo/plugin.php: <?php class plugin_foo extends plugin { /* ... */ function foo($ARGS) { /* source code of action "foo" */ if ($test) { return true; /* true = SUCCESS -> go to action "foo_read" */ } else { return false; /* false = ERROR -> go to action "foo_edit" */ } } function foo_edit($ARGS) { /* source code of action "foo_edit" */ } function foo_read($ARGS) { /* source code of action "foo_read" */ } } ?>
First you should have a basic structure generated for your new plug-in, with the help of the SDK. This makes the adaptation of the source code a lot simpler.
For adding a new action two things are necessary. On the one hand the source code and on the other hand, registration of this new action in the framework, by publishing the interface. This will allow the framework and all other plug-ins to find and use the action.
Note: All public functions of the class make a function available, which is called likewise, like the function. For example: in order to add an action "foo", you provide a function "foo()". You may run this by using the URL: "index.php?action=foo".
In the following example pay attention to the "Hot Spots". These mark places in the skeleton of the application, where you can fill in your own code.
<?php class plugin_example extends plugin { function plugin_example($plugin_name) { settype($plugin_name,"string"); global $YANA; $this->plugin_name = $plugin_name; } /** * Default event handler * * @param string $event name of the called event in lower-case * @param array $ARGS array of params passed to the function * @return boolean */ function _default($event, $ARGS) { settype($event, "string"); settype($ARGS, "array"); # HOT-SPOT << you may define actions here # for example by using a Switch statement switch ($event) { case 'my_action1': # HOT-SPOT << code for action 'my_action1' break; case 'my_action2': # HOT-SPOT << code for action 'my_action2' break; } return true; } /** * Type: read * Permission: 0 * Templates: index entries * * @param array $ARGS array of params passed to the function * @return boolean */ function guestbook_read_entries ($ARGS) { # HOT-SPOT << code for action 'guestbook_read_entries' } /* { ... } */ /** * Type: write * Permission: 100 * Templates: MESSAGE * * @param array $ARGS array of params passed to the function * @return boolean */ function guestbook_write_write ($ARGS) { # HOT-SPOT << code for action 'guestbook_write_write' $YANA->message("OK", "200"); } function my_action ($ARGS) { # HOT-SPOT << code for action 'my_action' } } ?>
All "Hot-Spots", where the developer can write own source code or supplement existing code, are emphasized in this brief example. The function "my_action" is to demonstrate, how you can add own actions to the interface of the class and thus to the plug-in. Take a look at the function "_default". This function is inherited from the parent class "plugin" and serves as default event handler, which catches all events, that are send to the plug-in, but do not have a public event handler (function) in the classe's interface. In addition this function intercepts special cases, like events where the corresponding function is not public, the function is the class constructor, or it is inherited from the base class "plugin" like the public function "toString()". For security reasons this also applies to inherited functions which are reimplemented in the child class. The reason is, these methods are public. They are part of a common interface of all classes who inherit from the parent class "plugin". Thus the functionality of these functions is guaranteed by the given semantics of the base class. These semantics should be kept in the child class.
In order to create a function, which is not to part of the public interface, mark the function as "private".
Please note: you need to refresh the plug-in cache to enable new functions. To do so, log in as administrator, open the administration menu and click "reload list".
The "setup" menus can be found in the column "options" (left) of the administration menu, as shown in the following illustration.
To create such a menu and setup, it is enough to first: add an entry to the configuration file of the plug-in and second: create an HTML template. It is not necessary to write PHP code. Look at the following example of the configuration file of a plug-in:
File "plugins/example.config" <INFO> /* first the mandatory data - these have nothing to do * with the setup menu itself. */ <ID>foo</ID> <NAME>Foo plugin</NAME> <AUTHOR>Thomas Meyer</AUTHOR> <DESCRIPTION>my example</DESCRIPTION> <LOGO>%PLUGINDIR%foo/preview.jpg</LOGO> <IMAGE>%PLUGINDIR%foo/icon.png</IMAGE> <TYPE>primary</TYPE> /* Here come the entries for the setup menu */ <SETUP> /* The following code will create an entry * labeled "Foo Options" using the icon * "plugins/foo/setup1.gif". By clicking the * button "Setup", the action * "foo_setup_1" is triggered. */ <0> <ACTION>foo_setup_1</ACTION> <TITLE>Foo Options</TITLE> </0> /* A second example: */ <1> <ACTION>foo_setup_2</ACTION> <TITLE>Bar Options</TITLE> </1> </SETUP> </INFO> <INTERFACE> /* The actions "foo_setup_1" <und "foo_setup_2" * are now associated with a template */ <foo_setup_1> /* TYPE=config defines, that this is a setup menu */ <TYPE>config</TYPE> /* PERMISSION=100 restricts access to users * with security level 100 - these are administrators. */ <PERMISSION>100</PERMISSION> /* foo_template_1 is the name of the template */ <INSERT>foo_template_1</INSERT> </foo_setup_1> <foo_setup_2> <TYPE>config</TYPE> <PERMISSION>75</PERMISSION> <INSERT>foo_template_2</INSERT> </foo_setup_2> /* ... other actions might follow here ... */ </INTERFACE>
Now all you need is a HTML page containing a form, like in the following example:
File "skins/default/example.html"
<!-- Just copy the head of the file. Here is nothing
to be edited. Copy'n'paste will be enough. -->
<!-- BEGIN: Head -->
<form method="POST" enctype="multipart/form-data" action="[%$PHP_SELF%]">
[%if !$ID%][%if $PERMISSION==100%]
<input type="hidden" name="action" value="set_config_profile">
[%/if%][%else%]
<input type="hidden" name="action" value="set_config_profile">
[%/if%]
<input type="hidden" name="[%$SESSION_NAME%]" value="[%$SESSION_ID%]">
<input type="hidden" name="id" value="[%$ID%]">
<!-- END: Head -->
<!-- Now the part where you need to fill your information -->
<!-- adding a headline is a good idea: -->
<h1>Setup</h1>
<!-- a list of options -->
<label>Option 1
<!-- $PROFILE.OPT1 is the stored value of this field - if any value has been stored yet.
The modifier |entities ensures, that tags and quotes
are converted to HTML entities, so that
HTML syntax errors are avoided.
-->
<input type="text" name="opt1" value="[%$PROFILE.OPT1|entities]">
</label><br>
<label>Option 2
<input type="text" name="path.opt2" value="[%$PROFILE.PATH.OPT2|entities]">
</label><br>
<label>Option 3
<input type="text" name="opt3" value="[%$PROFILE.OPT3|entities]">
</label><br>
<!-- don't forget the submit button -->
<input type="submit" value="[%$LANGUAGE.BUTTON_SAVE%]">
</form>
This HTML page causes the following array to be stored on submission of the form:
<?php $PROFILE = array( 'opt1' => "foo", 'path' => array( 'opt2' => "bar" ), 'opt3' => "foobar" ); ?>
You can access the stored options from within your plug-in as follows:
<?php global $YANA; $opt1 = $YANA->getVar('PROFILE.OPT1'); $path_opt2 = $YANA->getVar('PROFILE.PATH.OPT2'); $opt3 = $YANA->getVar('PROFILE.OPT3'); ?>
To have the form "example.html" shown when clicking the button "setup", it needs to be associated with the action "foo_setup_1". This is done as follows:
File "skins/default/example.config"
<!-- First the template with the name "foo_template_1" is defined ... -->
<foo_template_1>
<FILE>example.html</FILE>
</foo_template_1>
File "plugins/example.config"
...
<INTERFACE>
<foo_setup_1>
<TYPE>config</TYPE>
<PERMISSION>100</PERMISSION>
<!-- ... Next the template "foo_template_1" is associated with the action "foo_setup_1". -->
<INSERT>foo_template_1</INSERT>
</foo_setup_1>
...
</INTERFACE>
<?php $image = new Image('file name'); // for example: $image = new Image('folder/file.png'); ?>
<?php $content = file_get_contents('folder/file.png'); $image = new Image($content, 'png'); ?>
<?php $image = new Image(); $width = 640; $height = 480; $image->resize($width, $height); ?>
<?php $image = new Image('folder/file.png'); $width = 50; // if $height is not defined, // the image is resized proportional $image->resize($width); $image->outputToFile('folder/thumbnail.png'); ?>
<form method="POST" action="%PHP_SELF%" enctype="multipart/form-data">
(...)
Upload image: <input type="file" name="my_image" />
(...)
</form>
<?php // get data from form field 'my_image' $id = 'my_image'; // output to directory 'foo/bar/' using filename 'image' // (extension will be determined automatically) $file = 'foo/bar/image'; // output as png image $type = 'png'; // limit upload to 150 kbyte $size = 150000; // resize to 150px x 200px $width = 150; $height = 200; // leave aspect-ratio untouched $ratio = true; // set background color to gray $color = array(80, 80, 80); // call method $result = Image::uploadFile($id, $file, $type, $size, $width, $height, $ratio, $color); // check for errors if (is_string($result)) { print "The image was successfully uploaded to '$result'"; // error handling } else { switch ($result) { case UPLOAD_ERR_SIZE: case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: die('File too big!'); break; case UPLOAD_ERR_NO_FILE: die('No file has been uploaded!'); break; case UPLOAD_ERR_INVALID_TARGET: die("Unable to write to file: '$file'"); break; case UPLOAD_ERR_FILE_TYPE: die('The file is not a recognized image!'); break; default: die('Some unexpected error occurred!'); break; } } ?>
<?php // create an empty image $image = new Image(); $width = 180; $height = 120; $image->resize($width, $height); // set line width to 3px (optional) $image->setLineWidth(3); // fill canvas with background color $x = 0; $y = 0; $image->fill($image->white, $x, $y); // draw a line $x1 = 10; $y1 = 10; $x2 = 100; $y2 = 30; $image->drawLine($x1, $y1, $x2, $y2, $image->black); // draw a circle $x = 30; $y = 50; $width = 50; $image->drawEllipse($x, $y, $width); // draw a rectangle $x = 10; $y = 60; $width = 100; $height = 50; $border = $image->black; $fill = $image->navy; $image->drawRectangle($y, $y, $width, $height, $border, $fill); // draw a triangle (or other polygon) $points = array( 0 => array( 20, 0 ), 1 => array( 40, 20 ), 2 => array( 0, 20 ) ); $x = 50; $y = 80; $border = $image->black; $fill = $image->yellow; $image->drawPolygon($points, $x, $y, $border , $fill); // write a text $text = 'Hello World'; $x = 70; $y = 40; $color= $image->black; $image->drawString($text, $x, $y, $color); // Use of brushes // create new brush $brush = new Brush('small star'); // set brush size to 10px $brush->setSize(10); // set brush color to red $brush->setColor(255, 0, 0); // select the brush $image->setBrush($brush); // draw a dot using the brush $x = 150; $y = 100; $image->drawPoint($x, $y, IMG_COLOR_BRUSHED); // output file to browser $image->outputToScreen('png'); exit; ?>
Figure: Script output
If you work with images, you need colors to draw lines, text or surfaces.
There are 17 predefined colors. These are:
You can get other colors by calling: $color = $image->getColor($red, $green, $blue); where $red, $green, $blue are integers between 0 and 255.
It is important to you understand, that a color is not an object by itself, but a specific property of a graphic. This is, each image has its own color palette and each color is a part (mathematically speaking an "element") of this palette.
The palette of the image is an indexed set of all contained in the graphic colors. A color is represented by an integer. To be more precise: it is the index number of this color within the color palette of the image. This means, number 1 names the color, which is stored at position 1 of the image's color palette. For example, this color might be "red" in this image. However, you should note, that depends on the color palette and differs from one image to another.
Here is a list with examples for some interesting methods of the class image:
<?php $image = new Image('foo.png'); /* set brightness * * integer of -1.0 trough +1.0 (-100% and +100%) */ $amount = 0.5; /* 0.5 = +50% */ $image->brightness($amount); ?>
Figure: Script output
<?php /* set contrast * * integer of -1.0 trough +1.0 (-100% and +100%) */ $amount = 0.5; /* 0.5 = +50% */ $image->contrast($amount); ?>
Figure: Script output
<?php /* blur the image * * integer of 0.0 trough +1.0 (0% and +100%) */ $amount = 0.8; /* 0.8 = 80% */ $image->blur($amount); ?>
Figure: Script output
<?php /* sharpen the image * * integer of 0.0 trough +1.0 (0% and +100%) */ $amount = 0.8; /* 0.8 = 80% */ $image->sharpen($amount); ?>
Figure: Script output
<?php /* set to 256 colors grayscale palette */ $image->toGrayscale(); ?>
Figure: Script output
<?php /* colorize the image * * $red, $green, $blue = integer -255 through 255 * * the given color value is added to colors of the image */ $red = -80; $green = -40; $blue = 120; $image->colorize($red, $green, $blue); ?>
Figure: Script output
<?php /* multiply * * $red, $green, $blue = integer 0 through 255 * * the given color value is multiplied to the colors of the image */ $red = 100; $green = 255; $blue = 50; /* this examples reduces the red- and clue channels */ $image->multiply($red, $green, $blue); ?>
Figure: Script output
<?php /* monochromatic filter * * $red, $green, $blue = integer 0 through 255 * * Colorized the image using the given color. */ $red = 130; $green = 180; $blue = 200; $image->monochromatic($red, $green, $blue); ?>
Figure: Script output
<?php /* invert color values (negate) */ $image->negate(); ?>
Figure: Script output
For more details and examples see the API documentation.
The Yana Framework provides predefined CSS to create text boxes or pages with 2, 3 or more vertical columns. No programming is required. The required CSS code will be loaded automatically in the "default" skin theme.
Below are some examples for demonstration
Source code for the template:
<div class="multicol2">
<div class="col_left">
<h2>Column 1</h2>
Nulla ultrices lacinia mi. Nulla dapibus, risus vitae imperdiet
commodo, ipsum ligula lacinia orci, non venenatis metus ligula
sit amet turpis. Nunc accumsan tempor nulla. Vivamus eleifend,
s eu feugiat consequat, mi lacus vestibulum velit, eu
ege
egestas purus nunc ac est. Mauris massa lorem, lacinia non,
condimentum vel, cursus ac, lectus. Nulla tempor molestie quam.
Aenean dapibus nisl nonummy quam. Mauris pellentesque ornare
ante. Integer a urna ultricies neque bibendum dignissim.
Suspendisse interdum nisl. In rhoncus. Vivamus risus mi, semper
id, lobortis et, auctor in, nulla. Sed placerat posuere tortor.
In vitae augue.
</div>
<div class="col_right">
<h2>Column 2</h2>
Aliquam auctor viverra ligula. Ut nisi felis, condimentum at,
aliquam eget, tempus id, nisi. Maecenas a libero. Nulla metus
mi, malesuada sit amet, sagittis bibendum, scelerisque a, purus.
Nunc ipsum. Sed vel augue pellentesque ligula pharetra pulvinar.
Class aptent taciti sociosqu ad litora torquent per conubia
nostra, per inceptos hymenaeos. Nam orci. Donec neque pede,
iaculis in, pellentesque ac, commodo eget, libero. Sed vel
magna.
</div>
<div class="col_foot"> </div>
</div>
Source code for the template:
<div class="multicol3">
<div class="col_left">
<h2>Column 1</h2>
Nulla ultrices lacinia mi. Nulla dapibus, risus vitae imperdiet
commodo, ipsum ligula lacinia orci, non venenatis metus ligula
sit amet turpis. Nunc accumsan tempor nulla. Vivamus eleifend,
s eu feugiat consequat, mi lacus vestibulum velit, eu
ege
egestas purus nunc ac est. Mauris massa lorem, lacinia non,
condimentum vel, cursus ac, lectus. Nulla tempor molestie quam.
Aenean dapibus nisl nonummy quam. Mauris pellentesque ornare
ante. Integer a urna ultricies neque bibendum dignissim.
Suspendisse interdum nisl. In rhoncus. Vivamus risus mi, semper
id, lobortis et, auctor in, nulla. Sed placerat posuere tortor.
In vitae augue.
</div>
<div class="col_center">
<h2>Column 2</h2>
Aliquam auctor viverra ligula. Ut nisi felis, condimentum at,
aliquam eget, tempus id, nisi. Maecenas a libero. Nulla metus
mi, malesuada sit amet, sagittis bibendum, scelerisque a, purus.
Nunc ipsum. Sed vel augue pellentesque ligula pharetra pulvinar.
Class aptent taciti sociosqu ad litora torquent per conubia
nostra, per inceptos hymenaeos. Nam orci. Donec neque pede,
iaculis in, pellentesque ac, commodo eget, libero. Sed vel
magna.
</div>
<div class="col_right">
<h2>Column 3</h2>
Morbi eget dolor. Etiam ut velit sollicitudin massa laoreet
lobortis. ices.
Morbi justo tortor, blandit Morbi justo
tortor, blandit sed, euismod at, eleifend nec, tortor. Lorem
ipsum dolor sit amet, consectetuer adipiscing elit. Etiam eget
enim. s venenatis volutpat.
Integer ipsum ante, porta eu
Integer ipsum ante, porta eu, porta vel, euismod in, magna.
Fusce dolor quam, lacinia eget, rutrum at, lacinia pulvinar,
risus. Nulla imperdiet, lorem facilisis lacinia suscipit, diam
mi aliquet dui, at fringilla lectus mauris sit amet ipsum.
Morbi eget sem quis est congue pulvinar. Vivamus fermentum
dolor nec nisi convallis posuere. Aliquam eu diam. Ut suscipit,
nisi quis vehicula ullamcorper, lectus lorem fermentum tellus,
ut egestas arcu sapien aliquam massa.
</div>
<div class="col_foot"> </div>
</div>
It is possible to have nested multicolumn boxes. The following example demonstrates how two 2-column layouts are nested to create a 4-column layout.
Source code for the template:
<div class="multicol2">
<div class="col_left">
<div class="multicol2">
<div class="col_left">
<h2>Column 1</h2>
Vestibulum tempor commodo mi. Integer sed nisi. Donec
nulla elit, commodo porttitor, semper eget, sagittis
viverra, urna. Duis pretium dui facilisis turpis.
Mauris vel arcu. Donec ut lorem vel lorem aliquet
commodo. Donec sollicitudin mattis lacus. Ut non magna
sit amet tortor viverra sagittis. Ut venenatis.
Vestibulum sodales sapien scelerisque erat. Integer
accumsan orci et tortor vulputate mollis. Curabitur
lacinia quam id libero. Nulla eu lorem eget mi mattis
tempus. Suspendisse consectetuer. Vivamus at risus.
Aenean malesuada.
</div>
<div class="col_right">
<h2>Column 2</h2>
Morbi id nisi. Proin fringilla eleifend mi. Nunc eget
elit. Nam ligula nibh, euismod blandit, nonummy eu,
porta non, magna. Ut id nisi id metus consectetuer
euismod. Vestibulum ante ipsum primis in faucibus orci
luctus et ultrices posuere cubilia Curae; Mauris
blandit ultrices eros. Fusce in nisl sit amet enim
consequat tristique. Sed facilisis eros convallis magna.
Pellentesque justo. Praesent nec libero at velit
malesuada nonummy. Mauris vitae lectus eget elit
euismod bibendum. Nulla nec ligula. Curabitur metus.
Integer lectus nulla, iaculis molestie, venenatis non,
suscipit commodo, est. Sed pulvinar, justo eu pulvinar
convallis, mi tellus pellentesque elit, eu aliquet
sapien ligula non mauris.
</div>
</div>
</div>
<div class="col_right">
<div class="multicol2">
<div class="col_left">
<h2>Column 3</h2>
Praesent eu risus. Fusce feugiat. Maecenas eget lacus
quis nisi blandit porttitor. Pellentesque sed turpis.
Nulla facilisi. Aenean convallis dolor eget elit. Donec
elementum, pede nec euismod varius, ante diam elementum
lectus, vitae iaculis orci libero quis tortor.
Vestibulum justo est, laoreet eu, consequat sed,
molestie at, mi. Fusce luctus, odio eu imperdiet
viverra, nunc arcu gravida ante, iaculis lacinia tortor
arcu ac felis. Quisque sagittis aliquam nisi. Aliquam
nonummy. Nulla mattis orci vel nunc. Nunc in odio.
Proin mi risus, lobortis ac, mollis vitae, lacinia
vehicula, nulla. Nunc nibh. Morbi laoreet pellentesque
justo. ent ultrices.
Spa
</div>
<div class="col_right">
<h2>Column 4</h2>
Nullam eget lectus. Aenean non augue auctor erat luctus
fermentum. Quisque aliquam eros vel ligula. Mauris pede
velit, mollis eget, adipiscing non, placerat nec, erat.
Vestibulum vel sem non orci porttitor congue. Cras
pretium eros eget dui. Donec gravida. Sed mattis
tincidunt enim. Donec aliquam, quam eu rhoncus gravida,
nibh nunc egestas nulla, lobortis interdum nisi dui id
nisi. Praesent tristique velit sit amet ante. Nam
malesuada, nisi in laoreet venenatis, dolor pede
vehicula nunc, tempus accumsan ante purus eget sapien.
Praesent aliquam, lorem sit amet volutpat faucibus,
ligula lorem auctor diam, vitae vulputate pede ligula
et leo.
</div>
</div>
</div>
<div class="col_foot"> </div>
</div>
The Yana Framework provides several solutions to create foldable tree menus. Extensive programming is not required.
The following options are available:
For both variants, you have the choice between two different layouts. This chapter contains three sections. First you will be shown how to write the HTML source code for both menus 1 and 2 by hand. Then the text will demonstrate how to use the template function "printUnorderedList" to have these menus created automatically.
This section describes how to create a foldable menu directly in HTML. The Yana Framework will take care of the functionality and presentation automatically.
This is the 1st layout proposal. It uses JavaScript and CSS to display the menu. Following is a 2nd layout proposal, which can be used alternatively and uses exclusively CSS without JavaScript.
The layout is defined in the file "skins/default/styles/menu.css" and may be controlled by modification of the following CSS-classes:
The presentation of the menu corresponds to the current recommendations and should be accepted by search engines.
Proceed as follows:
Figure: Menu presentation
<ul class="menu root">
<li class="entry"><a href="target.html">Link</a></li>
<li class="menu">
<div onclick="yanaMenu(this)">Menu 1</div>
<ul class="menu">
<li class="entry"><a href="1.html">Item 1</a></li>
</ul>
</li>
<li class="menu">
<div onclick="yanaMenu(this)">Menu 2</div>
<ul class="menu">
<li class="entry"><a href="2.html">Item 2</a></li>
<li class="menu">
<div onclick="yanaMenu(this)">SubMenu 2.1</div>
<ul class="menu">
<li class="entry">
<a href="2_1.html">Item 2.1</a>
</li>
<li class="entry">
<a href="2_2.html">Item 2.2</a>
</li>
<li class="entry">
<a href="2_3.html">Item 2.3</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="menu">
<div onclick="yanaMenu(this)">closed</div>
<ul class="menu" style="display: none;">
<li class="entry"><a href="2.html">(invisible)</a></li>
</ul>
</li>
</ul>
The attribute "onclick" here triggers opening and closing of the menu. "Style" attributes set special properties, where "display: none" hides the menu. Menus are enclosed by an element of CSS-class "menu". Menu items are marked by an element of CSS-class "entry". The header of a menu is enclosed in "div" tags, which also have an attribute "onclick". By clicking the headline, the menu can be opened or closed.
Tip: it is usually easier to begin with a simple list of the tags "ul", "li" and add the CSS classes and JavaScript code later, rather than writing the menu line by line. This will keep the code clearer while editing.
There is the following predefinition: all menus are initially open, if they are not explicitly marked as closed. Multiple menus may be open at the same time.
If you want only 1 menu to be open at the same time, add the following source code:
<script
type="text/javascript">
yana_menu_auto_close = true;
</script>
If you want to have all menus closed at start, use the following code:
<script
type="text/javascript">
yanaCloseAll();
</script>
Both options can be combined.
<script
type="text/javascript">
yana_menu_auto_close = true;
yanaCloseAll();
</script>
Figure: Menu presentation
<ul class="hmenu">
<li class="entry">
<a href="target.html">Link 1</a>
</li>
<li onmouseover="yanaHMenu(this,true)" onmouseout=
"yanaHMenu(this,false)" class="hmenu">
<div class="menu_head">
Menu 1
</div>
<ul class="hmenu">
<li class="entry">
<a href="1.html">Item 1</a>
</li>
</ul>
</li>
<li onmouseover="yanaHMenu(this,true)" onmouseout=
"yanaHMenu(this,false)" class="hmenu">
<div class="menu_head">
Menu 2
</div>
<ul class="hmenu">
<li class="entry">
<a href="2.html">Item 2</a>
</li>
<li onmouseover="yanaHMenu(this,true)" onmouseout=
"yanaHMenu(this,false)" class="hmenu">
<div class="menu_head">
Submenu 2.1
</div>
<ul class="hmenu">
<li class="entry">
<a href="2_1.html">Item 2.1</a>
</li>
<li class="entry">
<a href="2_2.html">Item 2.2</a>
</li>
<li class="entry">
<a href="2_3.html">Item 2.3</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="entry">
<a href="3.html">Link 2</a>
</li>
</ul>
The menu works with CSS and does not need JavaScript. However, JavaScript is used anyway, so that older browsers and browser with incomplete CSS support, such as Internet Explorer 6.0, are still able to view the page as expected.
Each menu is initially closed and will be temporarily opened when the user hovers it with the mouse.
The CSS classes have only few differences to those of the vertical menu. Just be sure to use the class "hmenu" instead of class "menu".
This is the 2nd layout proposal. The menus will open automatically when they are touched with the mouse. In Firefox, Opera and Co. opening and closing the menu is done entirely via CSS (without JavaScript). Only in Internet Explorer, including version 7, JavaScript has to be enabled, because the necessary CSS 2.1 pseudo-classes are not yet fully supported.
The layout is defined in the file "skins/default/styles/gui_array.css" and may be controlled via the following CSS classes:
Proceed as follows:
Figure: Menu presentation
<ul class="gui_array_list">
<li class="gui_array_list">
<a class="gui_array_value" href="target.html">Link 1</a>
</li>
<li class="gui_array_head"
onmouseover="this.className='gui_array_head_open'"
onmouseout="this.className='gui_array_head'">
<span class="gui_array_key">Menu 1</span>
<ul class="gui_array_list">
<li class="gui_array_list">
<a class="gui_array_value" href="1.html">Item 1</a>
</li>
</ul>
</li>
<li class="gui_array_head"
onmouseover="this.className='gui_array_head_open'"
onmouseout="this.className='gui_array_head'">
<span class="gui_array_key">Menu 2</span>
<ul class="gui_array_list">
<li class="gui_array_list">
<a class="gui_array_value" href="2.html">Item 2</a>
</li>
<li class="gui_array_head"
onmouseover="this.className='gui_array_head_open'"
onmouseout="this.className='gui_array_head'">
<span class="gui_array_key">SubMenu 2.1</span>
<ul class="gui_array_list">
<li class="gui_array_list">
<a class="gui_array_value" href="2_1.html">Item 2.1</a>
</li>
<li class="gui_array_list">
<a class="gui_array_value" href="2_2.html">Item 2.2</a>
</li>
<li class="gui_array_list">
<a class="gui_array_value" href="2_3.html">Item 2.3</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="gui_array_list">
<a class="gui_array_value" href="3.html">Link 2</a>
</li>
</ul>
The attribute "onmouseover" is used to open the menus, and the attribute "onmouseout" to close them. These attributes are not mandatory, but are here for backward compatibility with the Internet Explorer and older browsers.
The template function "printUnorderedList" presents the contents of a multidimensional arrays as a menu.
To do this, 2 steps are required. Initially create an array containing the entries of your menu in the PHP source code of your plug-in. Then add the function "printUnorderedList" to your template to have the desired menu generated.
Proceed as follows:
Figure: Menu presentation
<?php global $YANA; $array = array( 'target.html' => 'Link 1', 'Menu 1' => array( '1.html' => 'Item 1' ), 'Menu 2' => array( '2.html' => 'Item 2', 'Submenu 2.1' => array( '2_1.html' => 'Item 2.1', '2_2.html' => 'Item 2.2', '2_3.html' => 'Item 2.3' ), ), '3.html' => 'Link 2' ); $YANA->setVar('myMenu', $array); ?>
As you can see, the key to the arrays contain the URLs and the values contained the caption.
To generate the actual menu, proceed as follows:
For the parameter "value" provide the array containing the items of the menu. Use the parameter "layout" to choose between the 1st and 2nd layout. You should set the parameter "keys_as_href" to "true".
To handle actions YANA provides the function $YANA->handle(), which returns bool(true) on success and bool(false) otherwise.
<?php global $YANA; /* call action "foo" */ $bool = $YANA->handle("foo", $parameter); if ($bool) { print "The action 'foo' has been executed successfully."; } else { print "The call of 'foo' returned an error."; } ?>
This causes all files in the directory "cache/" to be deleted.
<?php global $YANA; /* empty server's template-cache */ $YANA->handle("clear_server_cache", array()); ?>
<!-- The following entry in the template is already enough: -->
<textarea id="inputfield" name="text"></textarea>
[%preview%]
Figure: Example of the representation in the Browser
"Micro Summaries" are "active bookmarks", meaning hyperlinks, whose text can be dynamically updated when the content of the website changes, to which the link points. The Firefox browser offers Micro Summaries automatically, if the user creates a bookmark of a page that has that feature.
Examples of this would be:
Other variants are of course possible.
Micro Summaries are supported by the Yana Framework. They are created using the utility-class "Micro Summary". Take a look at the following examples:
<?php /* creating a mirco-summary in your plug-in */ Microsummary::set($this->name, 'link text'); /* Please note: * The first parameter is an unique id, * which identifies the entry. * Here: $this->name is used as id, * because the name of the plug-in is unique. * However, it is possible to use any other text. * For example, when you want to create multiple entries for the * same plug-in. */ /* reading a micro-summary in your plug-in */ $microsummary = Microsummary::get($this->name); /* publishing a micro-summary in your plug-in, * so that the browser can find it, when viewing the page */ Microsummary::publish($this->name); /* To open the micro-summary in your browser: */ index.php?action=get_microsummary&target=guestbook /* For testing, the URL must be copied to your browser. * The argument "target" names the id of the micro-summary you want to view. */ ?>
"RSS-Feeds" are electronic, (mostly) automatically generated and updated lists, which may provide various information. A visitor may read these with any "RSS reader" software. Modern browsers already have this functionality built in, so that visitors no longer need additional software.
Examples for RSS-Feeds:
The Yana Framework supports the creation of RSS feeds for your applications. Therefor you can use the prepared classes "RSS" and "RSSitem". The following are some examples.
<?php /* To create a RSS feed for your plug-in, first create a new function. (Change the name as you see fit. Note that the name must be unique.) */ function my_rss_feed($ARGS) { $rss = new RSS(); // choose a title and description $rss->title = 'Mike Miller\'s News'; $rss->description = 'this is my RSS feed for my web site'; // add an entry $item = new RSSitem(); $item->title = 'Entry 1'; $item->link = 'http://any.url'; $item->description = 'A short text, describing the content, or a text excerpt.'; // add the entry to the RSS feed $rss->addItem($item); // a 2nd eintry $item = new RSSitem(); $item->title = 'another item'; $item->link = 'http://any.other.url'; $item->description = '2nd description'; $rss->addItem($item); // output the RSS feed to the browser print utf8_encode($rss->toString()); exit(0); } /* You can have a link to the RSS feed viewed in the application. To do so use the function RSS::publish() and the name of the function, that creates the RSS feed. Tip: The best place to do this is is the constructor of your plug-in.*/ function plugin_my_test($name) { RSS::publish('my_rss_feed'); // more source code may follow here } ?>
The following Tutorial will demonstrate, how to write your own plug-ins. Therefor the text will guide you through some practice, where you will create your first application with the Yana Framework, a so-called "Web log". The source code can be found in the appendix, at the end of the tutorial.
To complete all steps you will need approximately 1 hour. References to literature can be found at the end of each section. If you wish to skip a section, you will find prepared source code snippets after each step, which you may copy.
Before you start with this tutorial, you should make sure that you have finished some preparations, because otherwise you might get to a point of the tutorial, where you can not continue.
First, you need access to a web server with PHP. A local installation should be preferred, as this simplifies work. However, access to a web server via FTP will do it anyway.
You should also have the Yana Framework already installed and be able to log-in as an administrator. You must also have the "Software Development Kit" for the Yana Framework installed and activated.
Also I recommend you to install an appropriate editor for the Framework, either " PSPad " or " ConTEXT ". Syntax highlighting and code templates are available for both editors. Read the chapter on editors, for installation instructions. For the purpose of this tutorial, we will demonstrate all examples by using the software "PSPad".
Basic knowledge of the structure and functions of PHP and MySQL databases are assumed.
Databases are represented in the Yana Framework by so-called "database schemes". These schemes are descriptions of the structure of a database. In other words, the tables, their columns, indexes and constraints. This information is stored in files. These "database structure files" are stored in the directory "config/db" of the framework.
In addition - and this distinguishes the Yana Framework fundamentally from other frameworks - these files also contain a description of the semantics of a database. In other words, the context in which a column is visible or invisible, how the information is to be presented to the user, or what label a column should have.
To create a database for the Yana Framework you don't need an expensive modeling tool, and no SQL knowledge, but merely a simple text editor.
To create a new database first open a blank text file in PSPad. Set the syntax highlighting to "Yana Framework" (if you haven't installed the syntax highlighting files and code templates yet, see the chapter "editors for the Yana Framework" ).
The new database will contain a table "blog", which will store the items of a web log. Each entry will be contain an unique ID (primary key), the author's name, date of creation, a title and text.
There are code templates to help you with the preparation of the database. Enter the text "db" and press the buttons <CTRL> + <SPACE> simultaneously. This will show a list of code templates. This list has two columns: the left in bold is a "shortcut" that you can enter directly (as in this case "db") and on the right a description of the templates.
Figure: Insert code templates
Choose the template "database definition" and press <ENTER>. The document should now look like the following figure:
Figure: Database body
Now insert a table. To do this, press <STRG> + <SPACE> and select the "table definition" (shortcut "tbl"). Rename the table by selecting the table name as the title of the opening and closing tags. In this tutorial the table will be named "blog".
Figure: Table "blog"
Add a primary key to the table. A table can always have only 1 primary key. The primary key must always consist of exactly one column of type "integer", "float" or "string". Compound primary keys are not supported.
To add a column, click inside the container "CONTENT" of the table, and insert the code template "primary key" (shortcut "id"). Name the column "blog_id".
To define that the column "blog_id" is a primary key, you must specify the name of the column as the "PRIMARY_KEY".
If you have completed these steps successfully, your file should look like the following:
Figure: Inserting the primary key
Now add a column of type "string" for the title and a column of type "text" for the text of the entry. A column of type "time" for the date of creation, and another column of type "string" for the name of the author.
The source code should now look like this:
<USE_STRICT>true</USE_STRICT>
<READONLY>false</READONLY>
<TABLES>
<blog>
<PRIMARY_KEY>blog_id</PRIMARY_KEY>
<CONTENT>
<blog_id>
<TYPE>integer</TYPE>
<LENGTH>8</LENGTH>
<REQUIRED>AUTO</REQUIRED>
<DISPLAY>
<HIDDEN>true</HIDDEN>
</DISPLAY>
</blog_id>
<blog_title>
<TYPE>string</TYPE>
<LENGTH>255</LENGTH>
<DESCRIPTION>Title</DESCRIPTION>
</blog_title>
<blog_text>
<TYPE>text</TYPE>
<LENGTH>3000</LENGTH>
<REQUIRED>true</REQUIRED>
<DESCRIPTION>Text</DESCRIPTION>
</blog_text>
<blog_created>
<TYPE>time</TYPE>
<REQUIRED>AUTO</REQUIRED>
<DESCRIPTION>Date</DESCRIPTION>
</blog_created>
<blog_author>
<TYPE>string</TYPE>
<LENGTH>255</LENGTH>
<DESCRIPTION>Author</DESCRIPTION>
</blog_author>
</CONTENT>
</blog>
</TABLES>
Compare the source code above with your result.
The property" TYPE " is the data type of the column, " DESCRIPTION " is a label of the column to show to the user. The property "LENGTH" indicates the maximum length for the contents of the field. For columns of type "string" (in MySQL this is interpreted as "VARCHAR"), this is the maximum length in characters.
The property " REQUIRED " indicates whether a value is mandatory. This property can take three values: "true", "false" and "AUTO". Where "true" = is a required field, "false" = an optional field and "AUTO" = the value is generated automatically, if the user does not specify a value.
Or in other words: the "REQUIRED" indicates whether a column is "nullable". If "REQUIRED" is set to "true", the column will be set "NOT NULL" in MySQL and vice versa.
If you set the value to "AUTO" on a column of type "integer" you get an auto-incremented column in MySQL. The column "blog_id" in the example above uses auto-increment. (In MSSQL and DB2 this is realized as "identity", as "sequence" in PostgreSQL and as trigger in Oracle). The value "AUTO" on a column of type "time" causes the current time to be inserted as value.
A property, which has not been used yet in the example above is " DEFAULT ". This can be used to set a "default value", which will automatically be used if there is no other input given.
Finally, the property " DISPLAY ". This property has no equivalent in SQL. It controls the presentation of the column in the Framework. It identifies all forms in which the column is: visible, visible and editable, or not visible at all. This distinguishes between forms to: view, search, create and edit records in the table.
The above table will now be completed with some more information.
For example, you should specify that the columns "blog_title" (title) and "blog_text" (text) always need to be filled. To do this, add the property "REQUIRED" with the value "true" (shortcut "req").
The date of creation is inserted automatically. It should therefore not be visible when writing the entry for the user and not be edited afterwards. For the column "blog_created" (date) add a property "DISPLAY" (shortcut "disp"). Set the values "HIDDEN.NEW" and "READONLY.EDIT" to "true". Set all other values to "false".
You can, of course, store several blogs in a table. To do this you need an extra column, which identifies which blog an entry belongs to. This can be useful, for example, if you have several web pages per site and want to have a blog for each of them.
The functionality to distiguish between all these different blogs, and the assignment to individual web pages don't need to be programmed manually. The framework has prepared a template for this purpose.
First add a new column of type "profile" (shortcut "pid"). The column should be called "profile_id". Then enter a new line after the property "PRIMARY_KEY", add the property "PROFILE_KEY" (shortcut "prf"), and set the value of this property to "profile_id".
Compare your results with the following source code.
<USE_STRICT>true</USE_STRICT>
<READONLY>false</READONLY>
<TABLES>
<blog>
<PRIMARY_KEY>blog_id</PRIMARY_KEY>
<PROFILE_KEY>profile_id</PROFILE_KEY>
<CONTENT>
<blog_id>
<TYPE>integer</TYPE>
<LENGTH>8</LENGTH>
<REQUIRED>AUTO</REQUIRED>
<DISPLAY>
<HIDDEN>true</HIDDEN>
</DISPLAY>
</blog_id>
<blog_title>
<TYPE>string</TYPE>
<LENGTH>255</LENGTH>
<DESCRIPTION>Title</DESCRIPTION>
<REQUIRED>true</REQUIRED>
</blog_title>
<blog_text>
<TYPE>text</TYPE>
<LENGTH>3000</LENGTH>
<REQUIRED>true</REQUIRED>
<DESCRIPTION>Text</DESCRIPTION>
<REQUIRED>true</REQUIRED>
</blog_text>
<blog_created>
<TYPE>time</TYPE>
<REQUIRED>AUTO</REQUIRED>
<DESCRIPTION>Date</DESCRIPTION>
<DISPLAY>
<HIDDEN>
<NEW>true</NEW>
</HIDDEN>
<READONLY>
<EDIT>true</EDIT>
</READONLY>
</DISPLAY>
</blog_created>
<blog_author>
<TYPE>string</TYPE>
<LENGTH>255</LENGTH>
<DESCRIPTION>Author</DESCRIPTION>
</blog_author>
<profile_id>
<TYPE>profile</TYPE>
<LENGTH>128</LENGTH>
<REQUIRED>AUTO</REQUIRED>
<DISPLAY>
<HIDDEN>true</HIDDEN>
</DISPLAY>
</profile_id>
</CONTENT>
</blog>
</TABLES>
This ends the creation of the database Save your changes in the file "blog.config" and close the program.
If you would like a more detailed view on the issues discussed in this section, you will find instructions in the following articles:
The following section will look at the creation of the program codes.
If you reconstructed the tutorial up to this point, you now already possess a simple database. However, to "bring it to life", you still need the business-logic of the application itself, as well as an user interface to view forms and data. You will create both in the following section.
Now open the sitemap of the Yana Framework in a scriptable web browser, for example, Firefox 2.0. Click the link "Login" an log on the system as "administrator". You will need administrator privileges to proceed with the next step.
At the sitemap click the link "Software Development Kit". Should this not yet been installed, or enabled, please do it now. For more information see the chapter "Creating plug-ins and applications".
If you opened the link, you should be able to see the following form.
Figure: Front page of the "Software Development Kit"
First, enter a name (for example, "my blog"), some description, and select a logo for your plug-in. Using the tab "author" you can provide information about your name, your web site address and contact.
On the page "other data" set the "type of application" to "primary". This declares the plug-in to be a "main program". This is optional, but may increase performance.
Figure: Setting the type of application
Next click the tab "Database".
Figure: Selecting the database structure file
Click the button "choose". Select the file "blog.config", you created, as the source.
Additional information about your plug-in is not necessary at this point. Click on "Finish". The source code and the user interface for your first plug-in will now be generated. Next you can immediately try your recently created plug-in.
Open the administration menu in expert mode. In the right column, section "plugins", click on "refresh list." Look for the name of your plug-in in the list and activate it, by clicking the checkbox next to the plug-in's name. Click on the "Save changes".
The following figure shows this menu.
Figure: Activating a plug-in
Attention! If you DON'T have database support activated you DON'T need to take the following step.
If you activated the database support and for example use a MySQL database for storing the entries, then you must install the tables for the plug-in first. To do so, open the menu "Database setup" and select "install marked databases" from the drop-down menu. A list of options will be shown. Enable only the entry of the database of your plug-in and click on "save changes".
See the following figure:
Figure: Installing a table on your database
Next go back to the sitemap of the framework. Now the name of your plug-in should be visible in the main menu. Click this link. You should now see the starting page of your application, as shown in the following figure.
Figure: Start page of the new plug-in
The text "no entries found" indicates that there are no entries stored in your blog yet. Click on the link "new" to create a new entry.
Figure: Creating a new entry
Next you should see the following index page. (The figure shows the form with two entries)
Figure: Viewing the entries
There are alternative views. Below the table you will find a list with 4 different layouts. So just choose the one you like best.
Figure: Detailed view
Click the link "edit" to edit entries. You should see the following form.
Figure: Editing entries
Clicking on the columns changes the sorting of columns. You need to mark all rows, which would like to change, by clicking the check box in the left column. The last row has only empty fields, which you can use to create a new entry the fast and easy way.
This form has a special feature. If you set the property "entries per page" to the value "1", the view for the current entry is maximized, which is more comfort editing it, as the following figure shows.
Figure: Editing a single entry
If you wish, you can also try the other functions of your new plug-in.
As you could see, the Yana Framework has already done a big part of the necessary work for you. However, there are certainly some details that you might want to customize to your needs. The following section of the tutorial will discuss how to do this.
The following section will introduce the definition files for plug-ins and templates. It is demonstrated, how these files can be edited, in order to adapt them to personal needs.
As the first step is to be demonstrated, how to insert smilies (emot-icons) and embedded tags (to format text). Open the file "skins/default/blog/blog_default_new_blog.html" in PSPad. Please note, that the text "blog" should be replaced with the name of your project. Select the highlighter "Yana Framework Templates" from the list of the available highlighters.
For comparison see the following figure:
Figure: Templates in PSPad
The function "import" loads the tree menu from the file "index.html". The function "create" calls the form generator of the Yana Framework, to generate a form, where the user may add a new item.
For an explanation of the function "create" see the reference of template-functions and -modifiers.
There are predefined code snippets for templates too. Now insert a function "embeddedTags" (Shortcut "embTag") below the function "create". You don't have to provide any arguments for this function.
For an explanation of the function "embeddedTags" see the reference of template-functions and -modifiers.
The source code should now look like this:
<!-- Begin: menu -->
<div style="width: 250px; overflow: auto; float: left;">
[%import file="index.html"%]
</div>
<!-- End: menu -->
<!-- Begin: content -->
<div style="margin-left: 260px;">
[%create
template="new"
file="blog"
table="blog"
where=$WHERE
titles=""
on_edit="blog_write_edit_blog"
on_delete="blog_write_delete_blog"
on_new="blog_write_new_blog"
on_search="blog_read_search_blog"
on_download="blog_blog_download"
sort=$SORT
desc=$DESC
page=$PAGE
entries=$ENTRIES %]
[%embeddedTags%]
</div>
<!-- End: content -->
<!-- Begin: foot -->
<div style="clear: both;">
<!-- your text here -->
</div>
<!-- End: foot -->
Compare the source code above with your result.
If you open your plug-in again in a browser, it should look like this.
Figure: Presentation in a browser (Firefox 3)
You may want to optimize this presentation for a better look and feel. There are many ways to do this and at most it is a matter of taste - however, here is a suggestion.
<fieldset>
<legend style="font-size: 12px; font-weight: bold;">Text formatieren:</legend>
[%embeddedTags show="b,i,u,|,h,emp,code,hide,|,small,big,|,img,url,mail,-,mark,color,smilies"%]
</fieldset>
If you haven't known the tags "fieldset" and "legend" till now: These tags produce subsections within forms. The tag "legend" specifies the label of the subsection. All "Fieldsets" are presented as framed sections. The text enclosed by the "Legend"-tag is printed at the border.
Also this source code demonstrates the use of arguments for the function "embeddedTags". Pay attention to the argument "show" of the function "embeddedTags". This accepts the two special values "|" and "-". Where "|" creates a vertical separator and "-" creates a line-break.
Finally let us create a preview of the new entry above both fieldsets (shortcut "preview"). You may take the snippet as is, the arguments "height" and "width" are not required and should be deleted.
The resulting source code should now look like this:
<!-- Begin: menu -->
<div style="width: 250px; overflow: auto; float: left;">
[%import file="index.html"%]
</div>
<!-- End: menu -->
<!-- Begin: content -->
<div style="margin-left: 260px;">
[%create
template="new"
file="blog"
table="blog"
where=$WHERE
titles=""
on_edit="blog_write_edit_blog"
on_delete="blog_write_delete_blog"
on_new="blog_write_new_blog"
on_search="blog_read_search_blog"
on_download="blog_blog_download"
sort=$SORT
desc=$DESC
page=$PAGE
entries=$ENTRIES %]
[%preview%]
<fieldset>
<legend style="font-size: 12px; font-weight: bold;">Text formatieren:</legend>
[%embeddedTags show="b,i,u,|,h,emp,code,hide,|,small,big,|,img,url,mail,-,mark,color,smilies"%]
</fieldset>
</div>
<!-- End: content -->
<!-- Begin: foot -->
<div style="clear: both;">
<!-- your text here -->
</div>
<!-- End: foot -->
The preview automatically uses the most recently used textarea-field as the source for creating the preview. The representation is loaded via AJAX directly from the server, without having to reload the page.
The result will be presented in the browser as follows:
Figure: Presentation in a browser (Firefox 3)
Again click the menu option "browse". Note the sorting of the entries. By default they are sorted by the primary key in ascending order. This is usually not desired for a web log. Instead the entries should be sorted in descending order by the date of their creation. To correct this, open the file "skins/default/blog/blog_read_read_blog.html" in PSPad. Please note, that the text "blog" should be replaced with the name of your project. Select the highlighter "Yana Framework Templates" from the list of the available highlighters.
As stated before, the function "create" is used to call the form generator. This takes some parameters, which control the output. Set the parameter "sort" to "blog_created" to have the table sorted by the column "blog_created", which contains the date of creation. Set the parameter "desc" to the value "true", to get the results sorted in descending order.
Note that the parameter "where" is set to the variable "$WHERE". This will become important in a later stage of this tutorial.
The following source code snippet illustrates the result.
<!-- Begin: menu -->
<div style="width: 250px; overflow: auto; float: left;">
[%import file="index.html"%]
</div>
<!-- End: menu -->
<!-- Begin: content -->
<div style="margin-left: 260px;">
[%create
template="view"
layout=3
file="blog"
table="blog"
where=$WHERE
titles=""
on_edit="blog_write_edit_blog"
on_delete="blog_write_delete_blog"
on_new="blog_write_new_blog"
on_search="blog_read_search_blog"
on_download="blog_blog_download"
sort="blog_created"
desc=$DESC
page=$PAGE
entries=$ENTRIES %]
</div>
<!-- End: content -->
<!-- Begin: foot -->
<div style="clear: both;">
<!-- your text here -->
</div>
<!-- End: foot -->
Compare the source code above with your result.
Next the tree menu is to be adapted, so that the items "new" and "About ..." are only visible to users, who are logged in to the system and have a appropriate security level. You might want to change the text of the labels as well.
The source code should now look like this:
<ul class="menu root" id="_menu">
<li class="menu">
<div onclick="yanaMenu(this)">blog</div>
<ul class="menu">
<!-- [%if $PERMISSION >= 75%] -->
<li class="entry"><a href=[%"action=mein_plugin_read_edit_blog"|href%]>[%$LANGUAGE.MENU.EDIT%]</a></li>
<!-- [%/if%] -->
<!-- [%if $PERMISSION >= 0%] -->
<li class="entry"><a href=[%"action=mein_plugin_default_new_blog"|href%]>[%$LANGUAGE.MENU.NEW%]</a></li>
<!-- [%/if%] -->
<!-- [%if $PERMISSION >= 0%] -->
<li class="entry"><a href=[%"action=mein_plugin_read_search_blog"|href%]>[%$LANGUAGE.MENU.SEARCH%]</a></li>
<!-- [%/if%] -->
<!-- [%if $PERMISSION >= 0%] -->
<li class="entry"><a href=[%"action=mein_plugin_read_read_blog"|href%]>[%$LANGUAGE.MENU.VIEW%]</a></li>
<!-- [%/if%] -->
</ul>
</li>
<li class="entry"><a href=[%"action=about&target=mein_plugin&type=plugin"|href%]>[%$LANGUAGE.ABOUT%] ...</a></li>
</ul>
Having the IF-statements put in comments is a notation to improve the readability of the code when viewing the template file in a browser. For the template itself, it doesn't matter if you use these comments or not.
The variable $PERMISSION stores the security level of the visitor. Guests have by default the security level "0", Administrators are level "100". Between both you can assign more levels. For example, predefined is level "30" for the moderator's user group. Use this variable to decide, if certain menu items are to be visible or not. Now change the value "Permission" for the operations "new" and "about" to ">= 30" (the value 30 is by default used for the user group of moderators).
Since the variable $PERMISSION always has a value >= 0, you may remove all such checks to improve performance.
Compare your result with the following source code.
<ul class="menu root" id="_menu">
<li class="menu">
<div onclick="yanaMenu(this)">blog</div>
<ul class="menu">
<!-- [%if $PERMISSION >= 75%] -->
<li class="entry"><a href=[%"action=mein_plugin_read_edit_blog"|href%]>[%$LANGUAGE.MENU.EDIT%]</a></li>
<!-- [%/if%] -->
<!-- [%if $PERMISSION >= 30%] -->
<li class="entry"><a href=[%"action=mein_plugin_default_new_blog"|href%]>[%$LANGUAGE.MENU.NEW%]</a></li>
<!-- [%/if%] -->
<li class="entry"><a href=[%"action=mein_plugin_read_search_blog"|href%]>[%$LANGUAGE.MENU.SEARCH%]</a></li>
<li class="entry"><a href=[%"action=mein_plugin_read_read_blog"|href%]>[%$LANGUAGE.MENU.VIEW%]</a></li>
</ul>
</li>
<li class="entry"><a href=[%"action=about&target=mein_plugin&type=plugin"|href%]>[%$LANGUAGE.ABOUT%] ...</a></li>
</ul>
As you might have guessed already, this was just the 1st step. The menu options are hidden now. However the actions are still callable for someone who knows the right URL. A skillful user could do so to circumvent this check of his security level and execute the function anyway. The following section will demonstrate, how may change the configuration file of your plug-in, in order to prevent this.
Off with HTML templates and on to configuration files. Open the file "plugins/blog.config" in PSPad (note, that the text "blog" should be replaced with the name of your project). Select the highlighter "Yana Framework"
Figure: Presentation of the configuration file in PSPad
The container "INFO" contains the header of the configuration file. This block may look a little different, depending on the settings you made when creating the plug-in.
First take a look at the element "START", where you will find the name of the front page of your plug-in (blog_read_read_blog). This value corresponds to the value of the argument "action", which is attached to the URL. The value identifies the plug-in and the action, which is to be carried out by the plug-in. The name was generated automatically. If you want to use another front page instead, just open the page you want in your browser. Look in the address in the browser, search for the value of the argument "action" and copy this value to the field "START".
The container "INTERFACE" describes all actions, that your plug-in can process. For each action there is a tag, which has further properties to describe it's characteristics. The names were also generated automatically. Note that the names must be unique. Therefor it doesn't make much sense to call an action something like "insert", since this would possible be used multiple times with different semantics.
For an explanation of all tags used in this configuration file, as well as code examples, see the developer's cookbook, chapter "editing plug-ins".
In the next step you will take over the changes you made in the file "index.html" to the configuration file, in order to make the circumvention of your security restrictions impossible.
To do so, first search for the action "blog_default_new_blog" and set the value of the field "PERMISSION" to "30", as you did in the file "index.html". Now do the same for "blog_write_new_blog". Note: The action "blog_default_new_blog" presents the form, while the action "blog_write_new_blog" handles the input from it. Both actions belong to the same form and this is why both values have to be changed accordingly.
The file "index.html" did contain the item "list" that you removed (compare with the source code above ). This item was linked with the action "blog_read_read_blog". Now look for this action and remove it.
The container "INTERFACE" should now look like this.
<INTERFACE>
<BLOG_READ_EDIT_BLOG>
<TYPE>read</TYPE>
<MODE>0</MODE>
<INSERT>BLOG_EDIT_BLOG</INSERT>
<PERMISSION>75</PERMISSION>
<ONSUCCESS>
<GOTO>blog_read_edit_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_read_edit_blog</GOTO>
</ONERROR>
</BLOG_READ_EDIT_BLOG>
<BLOG_WRITE_EDIT_BLOG>
<TYPE>write</TYPE>
<MODE>0</MODE>
<TEMPLATE>MESSAGE</TEMPLATE>
<PERMISSION>75</PERMISSION>
<ONSUCCESS>
<GOTO>blog_read_edit_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_read_edit_blog</GOTO>
</ONERROR>
</BLOG_WRITE_EDIT_BLOG>
<BLOG_WRITE_DELETE_BLOG>
<TYPE>write</TYPE>
<MODE>0</MODE>
<TEMPLATE>MESSAGE</TEMPLATE>
<PERMISSION>75</PERMISSION>
<ONSUCCESS>
<GOTO>blog_read_edit_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_read_edit_blog</GOTO>
</ONERROR>
</BLOG_WRITE_DELETE_BLOG>
<BLOG_DEFAULT_NEW_BLOG>
<TYPE>default</TYPE>
<MODE>0</MODE>
<INSERT>BLOG_NEW_BLOG</INSERT>
<PERMISSION>30</PERMISSION>
<ONSUCCESS>
<GOTO>blog_default_new_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_default_new_blog</GOTO>
</ONERROR>
</BLOG_DEFAULT_NEW_BLOG>
<BLOG_WRITE_NEW_BLOG>
<TYPE>write</TYPE>
<MODE>0</MODE>
<TEMPLATE>MESSAGE</TEMPLATE>
<PERMISSION>30</PERMISSION>
<ONSUCCESS>
<GOTO>blog_read_read_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_default_new_blog</GOTO>
</ONERROR>
</BLOG_WRITE_NEW_BLOG>
<BLOG_READ_SEARCH_BLOG>
<TYPE>read</TYPE>
<MODE>0</MODE>
<INSERT>BLOG_SEARCH_BLOG</INSERT>
<PERMISSION>0</PERMISSION>
<ONSUCCESS>
<GOTO>blog_read_search_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_read_search_blog</GOTO>
</ONERROR>
</BLOG_READ_SEARCH_BLOG>
<BLOG_READ_READ_BLOG>
<TYPE>read</TYPE>
<MODE>0</MODE>
<INSERT>BLOG_READ_BLOG</INSERT>
<PERMISSION>0</PERMISSION>
<ONSUCCESS>
<GOTO>blog_read_read_blog</GOTO>
</ONSUCCESS>
<ONERROR>
<GOTO>blog_read_read_blog</GOTO>
</ONERROR>
</BLOG_READ_READ_BLOG>
</INTERFACE>
Compare the source code above with your result.
The following section will show you how to edit the PHP source code of a plug-in to add new, or modify existing functions.
In this section of the tutorial you will learn how to implement an automatically generated RSS feed for the 10 latest items in the blog. The tutorial takes you through the various stages of the creation.
Before you edit the PHP source code, you should register the new action in the interface of the plug-in. Only then it can be called in your web browser. On the one hand this serves performance and on the other hand it adds an additional safety restriction, in order to make it more difficult to smuggle foreign PHP code into your application to prevent and thus improve the security for all plug-ins.
In order to accomplish this step, open the file "plugins/blog.config" in PSPad (note that you should replace the name "blog" with the name of your plug-in). Select the highlighter "Yana Framework"
Add a new action to the container "INTERFACE" (Shortcut "act"). Name this action "blog_rss".
The source code should now contain the following new action:
<INTERFACE>
[...]
<BLOG_RSS>
<TYPE>default</TYPE>
<MODE>0</MODE>
<PERMISSION>0</PERMISSION>
<TEMPLATE>INDEX</TEMPLATE>
<INSERT></INSERT>
<ONSUCCESS>
<TEXT>200</TEXT>
<GOTO></GOTO>
</ONSUCCESS>
<ONERROR>
<TEMPLATE>ALERT</TEMPLATE>
<TEXT>500</TEXT>
<GOTO></GOTO>
</ONERROR>
</BLOG_RSS>
[...]
</INTERFACE>
Not all of the automatically generated fields are required. Adjust the definition of the action as follows:
<INTERFACE>
[...]
<BLOG_RSS>
<TYPE>read</TYPE>
<MODE>0</MODE>
<TEMPLATE>NULL</TEMPLATE>
<PERMISSION>0</PERMISSION>
</BLOG_RSS>
[...]
</INTERFACE>
The field "TYPE" with the value "read" tells you something about the intended behavior of this action. This type expresses that this action only requires read access to the database. This information may help to improve performance.
The field "MODE" with a value of "0" indicates that the standard operation mode is used. (The value "1" will make the program start in "safe mode" for this action).
The field "PERMISSION" with a value of "0" indicates that every visitor is allowed to access this action, without needing some higher security level.
The field "TEMPLATE" indicates, which template is to be selected for the action. While it is possible to change the template inside the PHP code, using this field in the configuration file is easier and supports readability.
The selected template "NULL" is a predefined special value. It expresses that "no template" is to be selected This is not necessary, because the output is created by an automatic RSS generator, with no need for a template.
This completes the preparations. The following section will look at the implementation in the PHP source code.
Open the file "plugins/blog/plugin.php" in PSPad (note that you should replace the name "blog" with the name of your plug-in). Select the highlighter "PHP".
To the class "plugin_blog" add a new function with the name "blog_rss". You can copy the following template to do so.
<?php class plugin_blog extends plugin { // [...] /** * blog_rss * * returns bool(true) on success and bool(false) on error * * Type: read * Permission: 0 * Templates: NULL * * @access public * @return bool * @name plugin_blog::blog_rss() * @param array $ARGS array of params passed to the function */ function blog_rss ($ARGS) { } // [...] } ?>
Make it a habit to always immediately document any new function you write, while the idea and details are still fresh in your memory. This has the advantage, that you have to review your code, which helps you to identify possible problems very soon.
In order to provide a RSS feed of the 10 most current contributions, you need to load them from the database. Therefor you have to query the table "blog". By setting the limit-clause to "10" you limit the maximum number of rows in the result set to 10. To ensure these are the latest entries you have to sort them by the date of creation in descending order. This means, sort them by the column "blog_created". To do so, use "order_by" and "desc".
The following source code demonstrates this:
<?php /* get entries from database */ $query = array( 'key' => 'blog.*', 'order_by' => 'blog_created', 'desc' => true, 'limit' => 10 ); $rows = $this->database->get($query); ?>
As you can see in this example, the Yana Framework has a query generator for producing simple SQL statements. So you don't need any SQL knowledge to be able to query the database with the Yana Framework The framework also takes care of mapping the real SQL statements to the syntax of your DBMS.
The Query Generator also does simple automated safety checks, does automatic quoting of input values and does plausibility checks on SQL queries, before they are ever send to the database. This reduces the risk of SQL-injections and thus increases the security of your application.
The result set, here $rows, consists of a multidimensional, associative array. Each entry corresponds to a row. Each row has one item per column.
A call to var_export($rows) demonstrates this. This output is as follows:
<?php array ( 2 => array ( 'BLOG_TITLE' => 'Test', 'BLOG_TEXT' => 'This is a test[br]This is a test[br]This is a test[br]This is a test', 'BLOG_AUTHOR' => 'Thomas Meyer', 'BLOG_CREATED' => '1173276511', 'PROFILE_ID' => 'default', 'BLOG_ID' => 2, ), 1 => array ( 'BLOG_TITLE' => 'Test', 'BLOG_TEXT' => 'This is a test[br]Just another test.[br][br]New line.', 'BLOG_AUTHOR' => 'Thomas Meyer', 'BLOG_CREATED' => '1173276442', 'PROFILE_ID' => 'default', 'BLOG_ID' => 1, ), ) ?>
You can tell by the index of the array, that the entries are sorted in reverse order. The column names are written in capital letters.
For further details and code examples see the developer's cookbook, chapter databases and the API documentation of the class "DbStream".
To create RSS feeds you may use the class "RSS". In order to produce a new feed, an instance of this class should be produced in the next step.
<?php $rss = new RSS(); $rss->title = 'Blog'; $rss->description = 'the 10 most recent blog entries'; ?>
Title and description of the RSS feed can be chosen freely. To do so use the properties "title" and "description", as in the following example.
Subsequently, an entry in the RSS feed is to be provided for each entry of the database. An entry is represented by the class "RSSitem". In order to produce an entry, a new instance of this class is produced and added to the RSS feed by using the function RSS::addItem(). The entries are stored in the RSS feed in the order in which they are inserted - regardless of the date, as date is an optional field.
The following source code demonstrates how to create new entries.
<?php foreach ($rows as $row) { $item = new RSSitem(); // Title $item->title = $row['BLOG_TITLE']; // Link $action = 'blog_read_read_blog'; $blog_id = $row['BLOG_ID']; $link = DisplayUtility::url("action=$action&blog_id=$blog_id", true); $link = str_replace(session_name() . '=' . session_id(), '', $link); $item->link = $link; // Text $item->description = $row['BLOG_TEXT']; // Date $item->pubDate = date('r', $row['BLOG_CREATED']); // add item $rss->addItem($item); } /* end foreach */ ?>
Take a closer look at the code that creates the link. It refers to the action, which is used to output the blog. This action is "blog_read_read_blog". As already mentioned, this name is generated automatically. The function "url()" as member of the utility class "DisplayUtility" creates, as the implies, an URL included the server's address, name and path of the script and all required parameters.
The resulting link, however, also contains the current session Id. It naturally changes with every call. Actually, this should not be a problem, but unfortunately there is a weakness of some modern RSS reader products. For some (not all) of them are programmed to interpret the link of a RSS record as it's unique id. Even if explicitly a GUID is given, it is sometimes ignored, and the link is used anyway. For this reason, it may not change, because these programs otherwise would mix it up. That is the reason why the session Id necessarily has to be removed from the link and that is also the reason why the primary key of the item appears in the link. This is not a limitation of the framework, but an unpleasant feature of some RSS reader products. Unfortunately you will have to live with it for now.
Further details on the use of this class can be found in the API documentation for the classes "RSS" and "RSSitem".
The following extract of the source code, shows the action in context:
<?php /* get entries from database */ $query = array( 'key' => 'blog.*', 'order_by' => 'blog_created', 'desc' => true, 'limit' => 10 ); $rows = $this->database->get($query); $rss = new RSS(); $rss->title = 'Blog'; $rss->description = 'the 10 most recent blog entries'; foreach ($rows as $row) { $item = new RSSitem(); // Title $item->title = $row['BLOG_TITLE']; // Link $action = 'blog_read_read_blog'; $blog_id = $row['BLOG_ID']; $link = DisplayUtility::url("action=$action&blog_id=$blog_id", true); $link = str_replace(session_name() . '=' . session_id(), '', $link); $item->link = $link; // Text $item->description = $row['BLOG_TEXT']; // Date $item->pubDate = date('r', $row['BLOG_CREATED']); // add item $rss->addItem($item); } /* end foreach */ print utf8_encode($rss->toString()); exit(0); ?>
How you could see from the above source code, each entry refers to the action "blog_read_read_blog" with the parameter "blog_id". Up to now this action does not possess this parameter. The following section will demonstrate, how to add this parameter to this action.
Search in the document for the action "blog_read_read_blog". It should currently contain the following source code:
<?php /** * blog_read_read_blog * * returns bool(true) on success and bool(false) on error * * Type: read * Permission: 0 * Templates: BLOG_READ_BLOG * * @access public * @return bool * @name plugin_blog::blog_read_read_blog() * @param array $ARGS array of params passed to the function */ function blog_read_read_blog ($ARGS) { /* check input data */ assert('is_array($ARGS);'); settype($ARGS, 'array'); /* global variables */ global $YANA; /* do something */ return true; } ?>
First, you should check whether the parameter "blog_id" is set, as this is not always the case. Since the column "blog_id" in table "blog" is of type "integer", you should also check, if the input value is numeric. Next you should "cast" the type of the input value to an integer value. The following source code demonstrates this.
<?php if (isset($ARGS['blog_id']) && is_numeric($ARGS['blog_id'])) { $id = (int) $ARGS['blog_id']; } ?>
At this point, you should remember ( see above ) that you edited the function "create" in the template file and set the parameter "where" to the variable "$WHERE". Because you have done that, you can now set the variable "$WHERE" in the plug-in to influence this parameter.
To set the value of a template var call the function Yana::setVar. Set the value of this variable to "blog_id=$id".
<?php if (isset($ARGS['blog_id']) && is_numeric($ARGS['blog_id'])) { $id = (int) $ARGS['blog_id']; $YANA->setVar('WHERE', "blog_id=$id"); } ?>
Finally, another feature is to be demonstrated. If a RSS feed is present, the framework will automatically create a graphics labeled "RSS" plus a link to the RSS feed of the application. To activate this feature all you need to do is set a variable. The name of this variable is "RSS_ACTION" and its value should be the name of the operation, which outputs the RSS feed. In this case "blog_rss".
The following figure shows how this symbol is displayed in a browser.
Figure: Presentation in a browser (Firefox 2.0)
The following extract of the source code, shows the action in context:
<?php /** * blog_read_read_blog * * returns bool(true) on success and bool(false) on error * * Type: read * Permission: 0 * Templates: BLOG_READ_BLOG * * @access public * @return bool * @name plugin_blog::blog_read_read_blog() * @param array $ARGS array of params passed to the function */ function blog_read_read_blog ($ARGS) { /* check input data */ assert('is_array($ARGS);'); settype($ARGS, 'array'); /* global variables */ global $YANA; $YANA->setVar('RSS_ACTION', 'blog_rss'); if (isset($ARGS['blog_id']) && is_numeric($ARGS['blog_id'])) { $id = (int) $ARGS['blog_id']; $YANA->setVar('WHERE', 'blog_id=' . $id); } return true; } ?>
Compare your results with the source code above.
This step concludes the successful creation of your first own plug-in.
In this Tutorial you learned, how to:
This ends the tutorial. Further information and code examples can be found in the developer's cookbook and the API documentation of the Yana Framework.
All in this source codes used in this tutorial can also be found in the following package web log plug-in
The following Tutorial will demonstrate, how to use Server2Go and the Yana PHP-Framework to put web application on a CD/DVD-ROM and start them from there.
To complete all steps you will need approximately 1/2 hour plus the time you might need for necessary downloads. References to literature can be found at the end of each section.
Before you start with this tutorial, you should make sure that you have finished some preparations, because otherwise you might get to a point of the tutorial, where you can not continue.
Server2Go is donation-ware and may be used, even for commercial applications, for free. The software is developed and maintained by Timo Haberkern.
Basic knowledge of the structure and functions of PHP and MySQL databases are assumed.
To be able to start a PHP program with the Yana Framework directly from CD-ROM, proceed as follows:
Change the file "library.php" as follows: define('YANA_CDROM', true);
If your program requires a MySQL database, do the following:
After installing your databases, continue with the configuration.
Then the package can be put on a CD-ROM and used immediately.
In the next chapter, you will learn how Server2Go and Yana PHP-Framework can be used together with the portable edition of the Firefox browser.
You have the opportunity to use the Firefox browser with Server2Go on a CD . The following guide explains how to do that.
Note: There is a ZIP archive containing all required configuration files, which you may use alternatively. Unpack the archive and replace the original files. This is it already. You just need put it on a CD-ROM and run your application.
However, if you don't want to use the archive and prefer to complete the steps by hand, then continue with the configuration as follows.
[FirefoxPortable]
FirefoxDirectory=App\firefox
ProfileDirectory=Data\profile
PluginsDirectory=Data\plugins
SettingsDirectory=Data\settings
FirefoxExecutable=firefox.exe
AdditionalParameters=
LocalHomepage=
DisableSplashScreen=true
DisableIntelligentStart=false
AllowMultipleInstances=true
SkipChromeFix=false
SkipCompregFix=false
WaitForFirefox=true
RunLocally=true
Information on the options used here can be found in the file "FirefoxPortable/Other/FirefoxPortableSource/Readme.txt". This file is delivered with Firefox Portable. You may set the value "AdditionalParameters" to "-contentLocale de-DE -UILocale de-DE" if you want to use the German version (or any other locale: just replace the argument "de-DE").Subsequently, the package can be put on CD-ROM.
This section describes the state of the document at the time of it's publication. Later documents may supersede this version. The most current version may be found online at the project website of the Yana Framework.
This document is not yet a final release and it's contents are subject to changes. We welcome anybody to comment on the current version of this work.
The current version of this document is maintained within in the Subversion repository. A list of changes may be extracted from the repository at any time.
Version | State | Date |
---|---|---|
unversioned | Working Draft | 2009-07-01 - 2009-08-31 |
0.5 | Call for Comments | 2009-09-17 - 2009-10-05 |
0.5.1 | Working Draft | 2009-10-06 - current |
0.6 | Call for Comments | next |
This section defines fixed terms used in this document.
ELEMENT database (description?, (include | table | view | form | function | sequence | initialization)*, changelog?) ATTRIBUTE name string title string charset string datasource string readonly bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | - | An unique name that identifies this database. Defaults to the filename. It should be lowercased and a valid XML and SQL identifier. |
title | string | - | - | The title is a label text that should be displayed in the UI when viewing this object. Note that this may also be a language token, which is to be translated to the selected language. |
charset | string | - | - | The preferred charset to use when creating database objects and communicating with the database. The charset may only be set upon creation of the database. If you decide to use an existing database, please note, that the charset, which is actually used, might be another. |
datasource | string | - | - | The interpretation of the data source attribute depends on the implementation. For the Yana Framework it is a named data source. You may set up named database connections via the administration panel. |
readonly | bool | - | no | You may set the database to be read-only to prevent any changes to it. Use this if you wish to create a database viewer, or CD-ROM application. |
A root element that specifies important properties and/or requirements. These may be used if the database is created via script, or if by a database client, opens a connection.
The charset is to be set during creation of the database. Typical charsets are: utf-8, ascii, iso-8859-1. Note that some DBMS have different writings of the same charset, or may support charsets that other DBMS won't. The implementation must provide a list of valid charsets and automatically convert them to the correct writing for the target DBMS. The implementation must provide a list of valid charsets and automatically convert them to the correct writing for the target DBMS.
The data source in general it is an identifier for a particular set of connection parameters to a specific database. his may either be a JNDI data source for Java, an ODBC data source for C#, or any other named data source for any other language.
When displaying an user interface (UI) for a database, the implementation may choose to display the database title attribute as a header.
ELEMENT description (#PCDATA)
The description serves two purposes: 1st is offline documentation 2nd is online documentation.
A description is always optional. Note that this may also be a language token, which is to be translated to the selected language.
The form generator may use the description to provide context-sensitive help or additional information (depending on it's implementation) on a automatically generated database application.
ELEMENT include (#PCDATA)
Database definitions may be split in several files and recursively included. E.g. this may be necessary if you wish to create a reference to another table, which was defined elsewhere.
The list of includes may contain either filenames, or identifiers, which can be converted to filenames by the application.
Note that you should only include database definitions that use the same data source.
The child nodes of the database tags of the included files should be included in order of appearance to the database node of the source file. The database nodes themselves are to be ignored. If the included file includes further files, then these should be handled recursively. If a file is listed multiple times, the file must be included only once. If a file recursively includes another, which has already been loaded, the file must be skipped and not be loaded twice. Included files are not allowed to redefine already existing objects. For example, an included file may not overwrite a table definition in the source file. If an already existing element is found, an error must be thrown.
ELEMENT sequence (description?) ATTRIBUTE name string start integer increment string min integer max integer cycle bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this sequence. It should be lowercased and a valid XML and SQL identifier. |
start | integer | - | - | A sequence always starts with an initial value. The value defaults to the minimal value for ascending sequences and to the maximal value for descending sequences. Note: the start value must lay within range of the minimal and maximal sequence number. |
increment | string | - | 1 | An increment value (or step width) specifies the number that is added to the sequence when calculating the successor of it's current value. |
min | integer | - | - | The minimal value is a lower boundary for a sequence. All sequence values must be larger or equal the minimal value. The minimal value may not be larger than the maximal value. The default is 1 for ascending and PHP_INT_MIN for descending sequences. |
max | integer | - | - | The maximum value is an upper boundary for a sequence. All sequence values must be smaller or equal the maximum value. The maximum value may not be smaller or equal the minimum value. The default is PHP_INT_MAX for ascending and -1 for descending sequences. |
cycle | bool | - | no | If a sequence is a number cycle and the value of the sequence reaches an upper- or lower boundary, it will be reset to the minimum value for an ascending sequence or the maximum value for a descending sequence. |
Sequences are integer values, combined with a successor function. They serve various purposes. Usually they are used to auto-generate unique id's.
Note that there are implicit and explicit sequences. For example, an implicit sequence is created when you create an auto-increment column. You must not specify implicit sequences, as these are created and maintained by the DBMS itself.
Also note that some DBMS interpret the integer 0 to be equal to NULL. Thus you are encouraged NOT to create sequences that may contain the value 0 at any time. In addition, some applications may reserve index 0 for default values (e.g. as in data-warehousing).
While sequences are part of the SQL-2003 standard, they are not widely supported by most vendors. Except for PostgreSQL, where they are well known feature. They may be simulated for other DBMS though.
ELEMENT initialization (#PCDATA) ATTRIBUTE dbms string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
Initializing SQL statements, which are carried out right after the database structure has been published on a database. The syntax may either be portable or DBMS-specific.
The following values are suggested for the DBMS-attribute: 'generic', 'db2', 'dbase', 'frontbase', 'informix', 'interbase', 'msaccess', 'mssql', 'mysql', 'oracle', 'postgresql', 'sybase', 'sqlite'.
ELEMENT table (description?, (grant* | primarykey | foreign* | trigger* | constraint* | declaration | index)*) ATTRIBUTE name string title string readonly bool inherits string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this table. It should be lowercased and a valid XML and SQL identifier. |
title | string | - | - | The title is a label text that should be displayed in the UI when viewing this object. Note that this may also be a language token, which is to be translated to the selected language. |
readonly | bool | - | no | You may set the table to be read-only to prevent any changes to it. |
inherits | string | - | - | Name of the parent table. |
A table is a set of column definition plus properties, which all columns of the table have in common like: triggers and indexes.
Table-Inheritance means the following: a source table FooBar extends the structure and data of a target table Bar with Foo elements. All rows in FooBar have any columns defined in Bar + all columns of FooBar. Bar remains unchanged for that. If the structure of Bar changes, the structure of FooBar changes as well. Each element of FooBar may also be interpreted as an element of Bar, but not vice versa.
Technically spoken: the primary key of the source table is a foreign key that points to the primary key of the source table.
This is unlike PostgreSQL where you may define multiple-inheritance with n parent tables - with all consequences and issues linked to such behavior.
ELEMENT grant EMPTY ATTRIBUTE role string user string level integer select bool insert bool update bool delete bool grant bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
role | string | - | - | The role a user plays inside a user group. |
user | string | - | - | The group a user belongs to. |
level | integer | - | - | The security level may be any integer number of 0 through 100. You may translate this to 0-100 percent, where 0 is the lowest level of access and 100 is the highest. |
select | bool | - | yes | Tells whether the user is granted to issue a select-statement on the database object. |
insert | bool | - | yes | Tells whether the user is granted to issue an insert-statement on the database object. |
update | bool | - | yes | Tells whether the user is granted to issue an update-statement on the database object. |
delete | bool | - | yes | Tells whether the user is granted to issue a delete-statement on the database object. |
grant | bool | - | yes | Tells whether the user may temporarily grant his security permissions to other users. |
The rights management provides 3 layers, each of which is optional in this document. An implementation must however implement at least one of these options.
In analogy to databases, there is also a grant option, which allows users to temporarily grant any right they own in person, to any other user. So a manager may grant (and later revoke) all his rights to an assistant while he is on vacation.
You may decide that every manager of Human Resources, who has at least a security level of 50 may create a new employee and view salaries, but that it requires a manager of HR with security level 80 to update them.
You may even skip any of the levels to perhaps allow anybody to view a catalog form, who has at least a security level of 1, or grant access for any member of the sales department to sales data.
You may precisely choose what each member may or may not do: select (view), insert (create), update (edit), delete.
Note that you may have multiple grant elements to define several alternatives. For example, users may either be a member of group sales OR a have the role of a company manager to view and edit sales information.
If no grant element is present, the element is supposed to be public. This means, no security checking is done. If at least 1 grant element exists, no user is allowed to carry out any operation unless there is an explicit grant that allows him to do so.
The Yana Framework implements a profile system on top of all that as well. Application profiles may define different subsidiaries inside your company. For example, Europe or Asia. If implementations should support the management of multiple independent instances on the same installation, it is recommended to implement comparable systematics.
ELEMENT primarykey (#PCDATA)
The primary key is the name of a single column inside the table that has unique values and will be used to identify each row within the table.
The software must check if the specified column exists. If it doesn't, it must throw an error.
Each table must have exactly 1 primary key. Compound primary keys are not supported.
ELEMENT declaration (array | bool | color | date | enum | file | float | html |image | inet | integer | list | mail | password | range | reference | set | string | text | time | timestamp |url)*
The declaration element is a simple container that may contain a number of column definitions.
ELEMENT trigger (#PCDATA) ATTRIBUTE name string dbms string on string insert bool update bool delete bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | - | An unique name that identifies this trigger. It should be lowercased and a valid XML and SQL identifier. |
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
on | string | - | before |
|
insert | bool | - | no | fire on insert statements |
update | bool | - | no | fire on update statements |
delete | bool | - | no | fire on delete statements |
Triggers are a database feature that allows to execute code on certain database events. They fire on insert, update, or delete statements, or a combination of those. Triggers are fired either before, after or instead of a statement. When triggered, the given code is executed by the database.
The implementation however depends heavily on the chosen DBMS. Not all DBMS support all features. For example: not all DBMS support a trigger to be executed "instead" of a statement, thus replacing it.
Note that a trigger implies a user defined function. Some DBMS require thus that the function is explicitly created and only the name of the function must be specified for the trigger. Other DBMS allow that you specify the function body along with the trigger.
If a trigger uses a functionality which is not supported by the chosen DBMS, the DBMS will throw an error when trying to create the trigger. The implementation itself does not need to check that.
Triggers, that use the DBMS-type "generic", must be emulated. In that case the XDDL-compliant software must execute the code instead of the database. The implementation itself may define the required syntax of the code. In such case it is recommended, that the attribute code for such a trigger is limited to be a valid callback (function- or method names). The Yana Framework requires the code attribute of emulated triggers to be a valid callback of a user-defined PHP-function. See the manual of the database API for more details on that topic.
ELEMENT constraint (#PCDATA) ATTRIBUTE name string dbms string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | - | An unique name that identifies this constraint. It should be lowercased and a valid XML and SQL identifier. |
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
A constraint is a boolean expression that must evaluate to true at all times for the row to be valid. The database should ensure that. For databases that don't have that feature, the DBMS-type "generic" may be used to simulate this.
If a constraint uses the DBMS-type "generic", and the XDDL-implementation can't ensure, that the database supports that feature, it must validate the constraint instead of the database. The implementation itself may define the required syntax of the code. On simulation column-level constraints may be validated equivalent to table-level constraints. The Yana Framework uses PHP as language for the definition of generic constraints. It provides an associative array $ROW, that contains copies of the values of the current row. The keys of this array are the lowercased column names.
ELEMENT bool (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | no | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Bool may have two values: true and false.
Boolean values are not supported by all DBMS but may be easily simulated using integer types.
Input fields of type boolean are presented as checkboxes on edit. When viewing the column, a graphic indicates the state of the field.
HTML:
<input type="checkbox" value="true" name="{name}"/>
undefined <input type="radio" value="*" name="{name}"/> null <input type="radio" value="true" name="{name}"/> false <input type="radio" value="false" name="{name}"/>
<select name="{name}"> <option value="*">null</option> <option value="true">true</option> <option value="false">false</option> </select>
ELEMENT default (#PCDATA) ATTRIBUTE dbms string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. |
In newly inserted rows, columns are set to the default value automatically, if no other input is provided.
The type of the value depends on the type of column. The physical default value also depends on the DBMS.
A good example is data type Boolean, which is natively supported by PostgreSQL and thus the physical default value would be true or false. For MySQL it is stored as TinyInt with the default values 1 or 0.
However the DBMS-independent (generic) default values would be true or false. The implementation must convert these values automatically.
ELEMENT color (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | no | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type "color" contain a hexadecimal color value with a preceding '#'-character. Example: #F0F0F0.
These columns may be implemented as text with a length of 7 characters.
The element should be displayed as input control. An additional input support element may be implemented to aid users.
HTML5:
<input type="color" {required="notnull"} value="{default}" name="{name}"/>
ELEMENT integer (description?, grant*, constraint*, default*) ATTRIBUTE name string autoincrement bool unsigned bool fixed bool length integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
autoincrement | bool | - | no | Auto-increment is a MySQL-feature, that may be emulated on other DBMS. It can however only be used on columns of type integer. You should note, that the user input takes precedence over the auto-increment feature, which defines a default value. |
unsigned | bool | - | no | An "unsigned" number must always be a positive value. This means, any value less 0 is invalid. An implementation must throw an error, if a negative value is given for an unsigned column. (Note that MySQL automatically and silently replaces an negative value by 0.) |
fixed | bool | - | no | This sets the zerofill-flag for MySQL. For fixed length numbers, the value must be expanded to the defined maximum number of digits, by adding leading zeros. If the attribute length is not set, the attribute fixed must be ignored. |
length | integer | - | - | The maximum number of digits. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | no | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Integer may contain any whole number, which can be displayed by the database and programming language.
The upper and lower boundaries for integer values depend on the type of system. In general: on 32-bit systems the displayable numbers are in the range [-2^31, +2^31]. For 64-bit systems numbers may be bigger [-2^63, +2^63]. But only if all your software supports 64-bit integer values.
Note! 64-bit and 32-bit applications may be incompatible. Especially when using a 64-bit database server with a 32-bit application or vice versa. Be warned that a number overflow or underflow may occur when converting a large 64-bit to a small 32-bit number. Note that this applies to dates and times as well!
Strings and numbers are displayed as input fields when edited. If the column is editable, the content is displayed as text.
HTML5:
<input type="number" step="1" {required="notnull"} name="{name}"/>
ELEMENT float (description?, grant*, constraint*, default*) ATTRIBUTE name string unsigned bool length integer precision integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
unsigned | bool | - | no | An "unsigned" number must always be a positive value. This means, any value less 0 is invalid. An implementation must throw an error, if a negative value is given for an unsigned column. (Note that MySQL automatically and silently replaces an negative value by 0.) |
length | integer | - | - | The maximum number of digits. |
precision | integer | - | - | Defines the length of the decimal fraction. When present, the attribute length must be set as well. The maximum number of full digits is: length - precision. Be aware, the precision may not be larger than the length of the number. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | no | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Float may contain any number, which can be displayed by the database and programming language. These may either be whole, or floating point, or fixed point numbers.
These may either be whole, or floating point, or fixed point numbers. Note that, for floating point values, "maximum" means the maximum number of digits including fraction. Thus you will loose precision for very large and very small numbers. For fixed point numbers the maximum and minimum numbers are reduced by the number of digits reserved for displaying the fraction.
Note! 64-bit and 32-bit applications may be incompatible. Especially when using a 64-bit database server with a 32-bit application or vice versa. Be warned that a number overflow or underflow may occur when converting a large 64-bit to a small 32-bit number. Note that this applies to dates and times as well!
Strings and numbers are displayed as input fields when edited. If the column is editable, the content is displayed as text.
HTML5:
<input type="number" {required="notnull"} name="{name}"/>
ELEMENT range (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool unique bool readonly bool min float max float step float title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
min | float | yes | - | Smallest valid value. |
max | float | yes | - | Largest valid value. |
step | float | - | 1.0 | Minimal step width. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Range may contain any number, which can be displayed by the database and programming language. These may either be whole, or floating point, or fixed point numbers.
This type must be treated in the same way as columns of type Float. The attributes "min" and "max" implicitly define one or several constraints. The column's value may not be less than "min" and may not be greater than "max". The attribute "max" may not be less than "min". The column must allow at least 2 valid values. This means: "min" + "step" must be less or equal "max". The value "max" - "min" should be a multiple of the value "step". The value "step" may not be negative.
Numbers of this type should be displayed as horizontal slide control ("imprecise number-input control"). The client may choose the exact presentation itself.
HTML5:
<input type="range" {required="notnull"} min="{min}" max="{max}" step="{step}" name="{name}"/>
ELEMENT string (description?, grant*, constraint*, default*) ATTRIBUTE name string length integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
length | integer | - | - | The maximum number of characters. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type String contain a single line of text.
Note that string values must not contain line breaks. These are the characters \f, \r and \n. For security reasons values must not contain the character \#0.
Strings and numbers are displayed as input fields when edited. If the column is editable, the content is displayed as text.
HTML5:
<input type="text" maxlength="{length}" {required="notnull"} name="{name}"/>
ELEMENT mail (description?, grant*, constraint*, default*) ATTRIBUTE name string length integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
length | integer | - | - | The maximum number of characters. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Mail may contain any valid e-mail address.
Mails are implemented as string values. The syntax of e-mail addresses is specified inRFC 822. For PHP implementations it is recommended to use the following code for validation: filter_var($email, FILTER_VALIDATE_EMAIL) === true. Other implementations may use the following regular expression: [\w\d-_\.]{1,}\@[\w\d-_\.]{2,}\.[\w\d-_\.]{2,}.
On edit the column is presented as input field. This is equal to the presentation of strings.
Values of type "mail" should automatically be encoded when shown in a browser, to make data theft more difficult. This applies to all displayed e-mail addresses. The Yana Framework utilizes a filter within the presentation layer to solve this issue. A manual intervention is not necessary.
HTML5:
<input type="email" maxlength="{length}" {required="notnull"} name="{name}"/>
ELEMENT url (description?, grant*, constraint*, default*) ATTRIBUTE name string length integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
length | integer | - | - | The maximum number of characters. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type URL are equivalent to type String, except that every input is checked to be a syntactically correct URL.
URLs are implemented as string values. The syntax of URIs is specified in RFC 3986. For PHP implementations it is recommended to use the following code for validation: filter_var($url, FILTER_VALIDATE_URL) === true.
Strings and numbers are displayed as input fields when edited. If the column is editable, the content is displayed as text.
HTML5:
<input type="url" {required="notnull"} name="{name}"/>
ELEMENT password (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
A column of type Password is used to store encrypted password information.
Passwords are hash strings. The values must be calculated from the input using a , like MD5. Passwords must not be stored as clear text.
Passwords are not shown as text, but as an input element of type "password".
HTML5:
<input type="password" {required="notnull"} name="{name}"/>
ELEMENT inet (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Inet offer the possibility to automatically store the IP address of the visitor.
Inet columns are implemented as string values. The datatype must support IPv4 and IPv6. The syntax of IPv6 is specified in RFC 2460.
For PHP implementations it is recommended to use the following code for validation: filter_var($inet, FILTER_VALIDATE_IP) === true. Other implementations may use the following regular expressions:
As a rule, columns of this type should not be editable. If they are, they use an input box for editing.
HTML5:
<input type="text" {required="notnull"} name="{name}"/>
ELEMENT text (description?, grant*, constraint*) ATTRIBUTE name string length integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
length | integer | - | - | The maximum number of characters. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Text may contain multiple lines of text. They are unbounded in length.
Note that the length may be limited for technical reasons. The physical data type depends on the chosen DBMS. Also the DBMS may disallow you to create an index on a text column, or might create a special full text index with specific properties.
For security reasons values must not contain the character \#0.
Any HTML special characters must be masked. Additionally the implementation may define input and output filters. E.g. these may be used to add emot-icons and/or prevent spam and flooding.
For example, the Yana Framework implements several filters to prevent obvious vandalism.
Multi-line texts use textarea fields for editing. If the column is not editable, it's contents are shown as text. If a text is too long, scrollbars are shown (CSS: "overflow: auto").
HTML5:
<textarea maxlength="{length}" {required="notnull"} name="{name}"/>
ELEMENT html (description?, grant*, constraint*) ATTRIBUTE name string length integer notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
length | integer | - | - | The maximum number of characters. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type HTML may contain multiple lines of hypertext in XHTML format.
You should keep in mind that hypertext may contain tags and entities. Determining the true length of such a text may be tricky.
No files (like images) may be attached to or embedded in a HTML value of the column. But the column may contain a tag that refers to a file stored elsewhere.
For security reasons values must not contain the character \#0.
Additionally the implementation may define input and output filters. E.g. to prevent XSS-attacks and vandalism.
To edit HTML columns, the implementation may present an inline HTML editor. The properties and behavior of this editor are not specified. For output, HTML columns must be shown as interpreted hypertext according to the XHTML 1.0 standard or a successor of this standard (e.g. HTML 5).
ELEMENT date (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Date are used to store a date without time or timezone.
The physical data type depends on the chosen DBMS. For DBMS which do not support such a type, it may be simulated using an integer.
When editing select boxes may be shown to ease the input.
Values are presented as text. The presentation may depend on the chosen language.
HTML5:
<input type="date" {required="notnull"} name="{name}"/>
ELEMENT time (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Time are used to store dates with time and timezone.
The physical data type depends on the chosen DBMS. For DBMS which do not support such a type, it may be simulated using an integer.
When editing select boxes may be shown to ease the input.
Values are presented as text. The presentation may depend on the chosen language and timezone.
HTML5:
<input type="datetime" {required="notnull"} name="{name}"/>
ELEMENT timestamp (description?, grant*, constraint*, default*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Timestamp are used to store dates with time in UTC.
This type is identical to Time, except for the technical implementation.
The physical data type depends on the chosen DBMS. Usually it is stored and returned as an integer.
Note that there may be issues with negative timestamps on some OS, like older Win32 systems.
Be warned not to mix the value 0 (1.1.1970) with NULL (undefined).
When editing select boxes may be shown to ease the input.
Values are presented as text. The presentation may depend on the chosen language and timezone.
HTML5:
<input type="datetime-local" {required="notnull"} name="{name}"/>
ELEMENT enum (description?, grant*, constraint*, default*, option+) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
The data type Enum is an enumeration type. The list of valid values can be defined using option elements.
Note that the default value (if provided) must be a valid enumeration option.
Enumerations are stored as strings. The value is equivalent to the value of the chosen option.
When editing such column, a select box is shown. Alternatively radio buttons may be used. The elements equal to the option elements of the tag.
HTML5:
<select name="{name}"> <option value="">null</option> <option value="1">1</option> <option value="2">2</option> ... <option value="n">n</option> </select>
ELEMENT option (#PCDATA) ATTRIBUTE value string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
value | string | - | - | Text or integer value of this option |
An option represents one valid element of an enumeration. It has a textual description (pcdata) and a value. Only the value of the target column is stored in the database. The description (pcdata) is only shown in UI (instead of the value).
The description represents the option as a human readable text. Note that this may also be a language token, which is to be translated to the selected language.
If an invalid or undefined enumeration item is found in the database, the UI must show the stored value without conversion. The implementation may additionally throw an error message.
ELEMENT set (description?, grant*, constraint*, default*, option+) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
The data type Set is equivalent to the type Enum, but permits the selection of multiple values.
Note that the default value (if provided) must be a valid enumeration option.
The physical data type depends on the chosen DBMS. Some DBMS have native support. Where available, the native type should be used. Otherwise the data may be stored as a comma-separated string. The values are equivalent to the values of the chosen options.
When editing such column, a list of check boxes is presented. Alternatively a select box may be used, that allows the selection of multiple options. In both cases the values and labels equal the option elements of the tag.
HTML5:
<label><input type="checkbox" name="{name}[]" value="1"> 1 </label> <label><input type="checkbox" name="{name}[]" value="2"> 2 </label> ... <label><input type="checkbox" name="{name}[]" value="n"> n </label>
<select multiple="multiple" name="{name}"> <option value="">null</option> <option value="1">1</option> <option value="2">2</option> ... <option value="n">n</option> </select>
ELEMENT list (description?, grant*, constraint*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
A column of type List is a numeric array of strings.
PostgreSQL has native support for arrays. For other DBMS this feature must be simulated. It may be implemented by storing the values as a serialized or comma-seperated string. The string should be deserialized when loaded and returned as an array.
On edit lists are presented as lists of input fields. Additionally a control element is shown to remove or add new entries.
If the element is not editable, then the elements are enumerated as a list.
The displayed list items may be numerated.
ELEMENT array (description?, grant*, constraint*) ATTRIBUTE name string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
A column of type Array is a (possibly multidimensional) array of strings.
For most DBMS this feature must be simulated. It may be implemented by storing the values as a serialized or comma-separated string. The string should be deserialized when loaded and returned as an array. In that case it is recommended to encode the string using JSON.
On edit arrays are presented as key-value pairs. Additionally a control element is shown to remove or add new entries.
If the element is not editable, then the elements are enumerated as a multidimensional list. Keys and values are optically separated from each other. The presentation may be implementation as a foldable tree menu.
ELEMENT file (description?, grant*, constraint*) ATTRIBUTE name string notnull bool maxsize integer readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
maxsize | integer | - | - | The maximum size of the file in bytes. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
The data type "file" is used to save files ("binary large objects").
The files should remain in the file system for better performance. In order to save disk space, a compression (like GZip) may be used. This compression also ensures that files stores on the server are not executable and a potential attacker can't misuse such upload fields to store malicious code on the server.
For security reasons the file should be stored in a place, where it is not directly accessible for a client. When you download the file should be unpacked automatically, so the user no disadvantages from the experiences compression and decompression not installed. To provide a smaller download size, the file may automatically be send as compressed data stream, if the user's browser supports this feature. The browser unpacks the file independently. A manual intervention is not necessary.
On edit, an upload field is shown to upload a new file, and a button to download the current one. The implementation may offer a preview for files, whose Mime-type is known.
ELEMENT image (description?, grant*, constraint*) ATTRIBUTE name string notnull bool width integer height integer ratio bool background string maxsize integer readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
width | integer | - | - | Contains the image horizontal dimension in pixel |
height | integer | - | - | Contains the image vertical dimension in pixel |
ratio | bool | - | no | This applies only to the process of resizing images, when the attributes height and width are given. Otherwise the attribute must be ignored. If this attribute is set to "yes", the image's aspect-ratio must be kept when resizing it. If set to "no": image must be stretched to the given size. |
background | hex-value | - | - | This applies only to the process of resizing images, when the attribute ratio is set to "yes" and the attributes height and width are given. In this case you may specify the background color here. Otherwise the attribute must be ignored. It must be a hexadecimal color value. Example: #f0a080 |
maxsize | integer | - | - | The maximum size of the image in bytes. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Image are files (binary large objects). These must be graphics of a supported type and should be displayable on the user interface as an image.
The graphics file must automatically be checked and converted during upload. If the given file is not a valid graphic, the implementation must throw an error message. When the image is resized, the implementation must respect the attributes "width", "height", "ratio" and "background". The dimensions of the image are adapted to the given height and width. If the attribute "ratio" is set to "no" and width and height are given, the image dimensions are asymmetrically stretched to the exact given values. Otherwise the width and/or height is changed, but the aspect ratio of the image is kept intact. If these changes result in a part of the canvas being empty, then this part is to be filled with the given background color. The background color is given by the attribute "background". If no background color is defined, the implementation may chose a color.
Note: not all image files are suited to be shown in a browser. For example, many browsers (and other programs) may have problems viewing images that use a CMYK color palette.
For reasons of performance, image files should be stored outside the database. Compressing bitmap graphics usually doesn't have any benefits and should be avoided.
A list of valid image formats and/or rules for their interpretation are not specified in this document. The treatment of vector graphics is not specified in this document.
Columns of the data type Image are presented as a thumbnail image plus an upload field to insert or replace the stored graphic. When clicking the thumbnail the complete graphic should be shown. For the creation of the thumbnail, the implementation should also respect the attributes ratio and background.
ELEMENT reference (description?, grant*, constraint*, default*) ATTRIBUTE name string table string column string label string notnull bool unique bool readonly bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this column. It should be lowercased and a valid XML and SQL identifier. |
table | string | - | - | Name of target table |
column | string | - | - | Name of value-column (must be unique). The values in this column are stored as the value of the reference. |
label | string | - | - | Name of label-column (should be unique). This column should contain human readable description, which are displayed to the user. |
notnull | bool | - | no | A not-null column may not contain undefined (NULL-)values. |
unique | bool | - | no | An unique constraint means, that the column must not contain duplicate values. An unique constraint means, that the column must not contain duplicate values. Note that a unique constraint technically implies an unique index on this column and vice versa. |
readonly | bool | - | no | You may set the column to be read-only to prevent any changes to it. Note that rows may still be inserted or deleted, but the column may not be updated. |
title | string | - | - | A text that may be used in the UI as a label for the control element associated with the column. Note that this may also be a language token, which is to be translated to the selected language. |
Columns of type Reference are used to represent foreign keys. The real type of the column depends on the type of the target column.
A reference itself does not automatically imply a foreign key constraint.
Only the value of the target column is stored in the database. The physical type and properties of the column are thus inherited from those of the target column. If the physical type of the target column changes, the physical type of the reference column must change too. If the target column has no suitable type, the implementation must throw an error.
When editing such column, a select box is shown. The options are filled with the entries of the referenced table. The labels are taken form the column "label" and the values are taken from the target "column" of the target table.
ELEMENT foreign (key+) ATTRIBUTE name string table string match string ondelete string onupdate string deferrable bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | - | An unique name that identifies this foreign key constraint. It should be lowercased and a valid XML and SQL identifier. |
table | string | yes | - | Name of target table |
match | string | - | simple | full | partial | simple |
ondelete | string | - | no-action | no-action | restrict | cascade | set-null | set-default |
onupdate | string | - | no-action | no-action | restrict | cascade | set-null | set-default |
deferrable | bool | - | no | Deferrable means, the DBS should wait till the end of a transaction before it checks inserted or updated foreign keys. This is meant for situations, where you push data in both: the parent and the child table within one transaction, or when you use circular references (if supported by your DBMS). |
Foreign-key constraints are meant to ensure referential integrity between tables. This feature is not supported by all DBMS. The implementation my emulate that feature.
Each foreign-key consists at least of a source and a target. Note that the types of the source columns of a foreign-key depend on the types of the target columns. Columns containing a foreign-key should thus be defined as type "reference". If so, the implementation must determine the correct type automatically.
The attributes "ondelete" and "onupdate" define, how the DBMS should react when an reference is updated.
The attribute "match" defines, how the DBMS should evaluate the given rules for referential integrity. This applies to compound foreign-keys including multiple columns only.
The attribute "deferrable" is not supported by some DBMS. Note that this feature may not be emulated. Various DBMS have different approaches to circumvent this problem. For example, temporarily deactivating constraints. See your manual for details.
The attribute "match" is not supported by some DBMS. This applies to compound foreign-keys including multiple columns only. This feature is rarely used and should be avoided for portable database applications for reasons of compatibility.
Some values of the attributes "onupdate" and "ondelete" are not supported by some DBMS. These may be simulated using triggers.
ELEMENT key EMPTY ATTRIBUTE name string column string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name of column in source table. |
column | string | - | - | Name of column in target table. It defaults to the primary key of the target table. |
A column reference including a source and target column. If a foreign-key constraint defines more then one column reference, it is a so-called "compound" foreign-key. Note that compound keys are more complicated to handle and are not supported by all DBMS. Compound keys should be avoided where possible.
ELEMENT index (description?, column*) ATTRIBUTE name string unique bool clustered bool title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | - | An unique name that identifies this index. It should be lowercased and a valid XML and SQL identifier. |
unique | bool | - | no | An unique index always implies an unique-constraint. An unique constraint means, that the column must not contain duplicate values. |
clustered | bool | - | no | Installs a setting, that all columns in the tablespace should be stored in the order of this index. This is a performance setting. |
title | string | - | - | The title is a label text that should be displayed in the UI when viewing this object. |
Indexes are meant to improve the performance of select-statements. An index is a sorted list of column values. Scanning an index is usually faster than scanning a whole table. However: as creating and maintaining an index causes some overhead in calculation time, insert- and update-statements will thus be slower when using an index.
Note: it is not required to explicitly define an unique-constraint on a primary key. Primary keys implicitly have an unique-constraint.
Important! Even if an unique index exists, the column is not reported to have an unique-constraint. It is recommended to avoid unique indexes, whenever it is possible to express the same using a constraint. Note that an unique constraint may not be defined using multiple columns. In that case you should use an unique index.
Clustered indexes apply to MSSQL only. A clustered index means, that the DBS should try to store values, which are close to each other in the index, close to each other in the tablespace, so that they fit inside the same memory page, when retrieving data from a table.
Typically a clustered index is created on the primary key (which is the default), or on another column which is used for sorting the table. Each table may only have one clustered index. If the attribute "clustered" of an index is to be set to "yes", the implementation must check, if the table has another clustered index already. If this is the case, the attribute "clustered" of this index must be set to "no". The implementation may throw an error message, if two clustered indexes are found within one table.
ELEMENT column EMPTY ATTRIBUTE name string sorting string length string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | The name of the indexed column in the source table. |
sorting | string | - | ascending | In an index, each column may be sorted separately for performance reasons. This is especially used for indexes with multiple columns.
|
length | integer | - | - | maximum length of index values |
The column list of an index specifies which columns of a table are indexed and how these values are stored.
The length attribute is used for performance optimization and is only supported by MySQL. Other DBMS don't support this argument. Note, that the attribute "length" may not be greater than the length of the source column.
The implementation may automatically decide if a full-text index is to be created, if the DBMS supports that feature.
ELEMENT view (description?, grant*, field+, select*)> ATTRIBUTE name string readonly bool tables string where string orderby string sorting string checkoption string title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this view. It should be lowercased and a valid XML and SQL identifier. |
readonly | bool | - | no | You may set the view to be read-only to prevent any changes. A view that is "read-only" is not updatable. |
tables | nmtokens | - | - | A comma-separated list of tables used in the current view. If the list contains more than one table, the first is the base table and all other tables are joined to this. For don't forget to define a where-clause for all joined tables. |
where | string | - | - | This is a generic information. It sets the where-clause of the view. |
orderby | string | - | - | A comma-separated list of columns, after whose the output is sorted. By default the table-output is ordered by it's primary-key. |
sorting | string | - | ascending |
|
checkoption | string | - | none |
|
title | string | - | - | The title is a label text that should be displayed in the UI when viewing this object. |
The "view" element defines the name, documentation and generic properties of a database view. These are it's base tables, where-clause and other information, which should allow an application to map elements of the view to elements of real tables. This should enable an implementation to simulate simple and updatable view for such DBMS, that don't support this feature. An implementation may use the generic information for the simulation of views. The implementation may throw an error message, if it is not possible to simulate the view and the DBMS doesn't support the requested feature.
The software must consider a view to be "updatable", unless the attribute "readonly" is set to "yes". It may however decide that the view is not updatable, if a required primary key is missing. Where applicable the implementation may automatically add a primary key column to the generic query, to make the view updatable. For DBMS that don't support updatable views, the implementation may simulate these by generating insert or update statements based on the given generic information. The implementation may throw an error if the user tries to update a view for such DBMS and it is unable to simulate the desired behavior.
The attribute "where" defined the where-clause of the view. This information must be taken into account when simulating the views. The implementation may define the required syntax of this element itself. Note! The following example is not recommended "time < now()" since the function "now()" may not be compatible between various DBMS. However, you may define explicit SQL-statements for any target-DBMS of your choice. The syntax for generic where-clauses, as supported by the Yana Framework, is: {[column]=[value]{ AND [column]=[value]}*}.
The attribute "checkoption" influences the evaluation of the where-clause. The difference between 'local' and 'cascaded' applies only to situations, where a view is built recursively upon another view and the parent view declares a check-option itself. If this is the case, the setting 'local' will prevent the DBS from recursively evaluating the check option(s) of the parent view(s). Note that this is not supported by all DBMS.
For example, MySQL and PostgreSQL both support this feature, while MSSQL does not.
ELEMENT select (#PCDATA) ATTRIBUTE dbms string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
The "select" element is a DBMS-dependent SQL-statement. It must comply with the defined names of the base tables, fields and other properties of the view. For simple views the generic information may already be enough, so that no additional select elements need to be defined. Thus you should avoid the select element where possible.
ELEMENT field EMPTY ATTRIBUTE table string column string alias string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
table | string | - | - | Name of base table |
column | string | - | - | Name of base column |
alias | string | - | - | optional alias |
A column reference, that identifies the name of a column in the view (see attribute "alias") with the names of the physical table and column it is based on.
ELEMENT form (description?, grant*, fieldset*, event*) ATTRIBUTE name string table string template string title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this form. It should be lowercased and a valid XML and SQL identifier. |
table | string | - | - | Name einer Quelltabelle oder eines Views. Note that the view should be updatable. Otherwise the form should not be editable |
template | string | - | - | The attribute template may contain any text. The implementation used to generate the forms may provide a list of valid values for this attribute. The template should provide all required information, which the implementation needs, to create the form. |
title | string | - | - | The title is a label text that should be displayed in the UI when viewing this object. |
Forms are named objects, that are used as input for a form- or GUI-generator. They must have at least a base table and a template. The base table may of course also be an (updatable) view.
Forms are not a database feature. They need to be generated by software for all known DBMS. The contents of the form however are based on the database schema defined in the XDDL file. Based on this information, the implementation should generate select-, update-, insert- or delete-statements automatically.
For HTML output, forms should be displayed as HTML form elements. The presentation of the form must be defined in the associated template.
ELEMENT fieldset (description?, grant*, input+) ATTRIBUTE name string title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | default | An unique name that identifies this fieldset. It should be lowercased and a valid XML and SQL identifier. |
title | string | - | - | The title is a label text that should be displayed in the UI when viewing this object. |
Fieldsets are meant to group fields. If a fieldset is unnamed, it's name defaults to "default". Thus note, that there must be no more than 1 unnamed fieldset per form. Throughout the form, each fieldset's name must be unique.
Fieldset elements should be presented in HTML using "fieldset" tags. The attribute title may be used as "legend" element for the tag.
ELEMENT input (description?, grant*, action*) ATTRIBUTE name string label string hidden bool readonly bool cssclass string tabindex integer
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this input field. It should be lowercased and a valid XML and SQL identifier. |
label | string | - | - | The label is a description field, which provides more information about the content of the input field. |
hidden | bool | - | no | The input field must not be visible in the UI when the value of this attribute is set to "yes". Otherwise the attribute is to be ignored. |
readonly | bool | - | no | You may set the input field to be read-only to prevent any changes. An input field that is "read-only" is not updatable. |
cssclass | string | - | - | A HTML-based UI should provide id attributes and CSS classes for fields automatically. Additionally this attribute may be used to add a user-defined CSS class. |
tabindex | integer | - | 0 | A HTML-based UI should provide the tab-index for fields automatically. Alternatively this attribute may be used to set a user-defined value. The tab-index defines the order in which fields should get the edit focus, when the user hits the tab-key on a keyboard. |
The input element is a single field inside a fieldset.
A HTML-based UI should display this as an "input" element, or something familiar, depending on the type of the underlying column. If the attribute "readonly" is set to "yes", the element should not be editable. The implementation should create CSS class- and/or id-attributes for each input field. Additionally if the attribute "cssclass" is set, this value has to be added to the class attribute of the tag. If the attribute "tabindex" is provided, it is to be copied to the attribute list of the control element. If the "input" element is represented by more then one HTML field, the tabindex should be set to the first editable field.
ELEMENT action (#PCDATA) ATTRIBUTE name string language string title string label string icon image
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | An unique name that identifies this action. It should be lowercased and a valid function name. |
language | string | - | - | The programming language of the action implementation. May be any string. If the option is not set, the implementation may decide itself how to handle the action. The Yana Framework will interpret the action as the name of a defined plug-in action. |
title | string | - | - | Title-attribute for clickable links. |
label | string | - | - | Text label used for clickable links. |
icon | image | - | - | Image file used as icon for clickable links. |
The action element is meant to be triggered when the user clicks the form field.
The presentation of the element depends on the attributes language and name.
If the language is set to PHP, or if the attribute language is empty, it is recommended to be displayed as an A-tag next to the form field. The content of the tag is the attribute "label" and/or an img-tag, where the src-attribute is taken from the "icon" attribute of the element. The href-attribute of the tag should be a reference to PHP_SELF plus the name of the action as parameter, provided as "action={name}". The value of the field should be provided as a second parameter "target[{primary_key}]={value}"
Example:
<action name="foo" label="click me" icon="common_files/icon.png"/> <a href="?action=foo&target[12]=bar"> click me <img src="common_files/icon.png"/> </a>
If the language is set to JavaScript, the name must be a valid JavaScript event and the pcdata section must be valid JavaScript code.
Example:
<action name="onchange" language="javascript">validate(this)</action> <input ... onchange="validate(this)"/>
If a label or icon is provided, the action should also be triggered when clicking the label and/or icon.
Example:
<action name="onchange" label="validate" icon="common_files/validate.png" language="javascript">validate('foo')</action> <input ... onchange="validate('foo')" /> <a onclick="validate('foo')"> validate <img src="common_files/validate.png"/> </a>
ELEMENT event (#PCDATA) ATTRIBUTE name string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name of event to be triggered (as defined by the chosen template) |
The event is triggered when the user performs a certain action in the form, like submitting the form, clicking a download or something similar. The available types of events must be defined by the chosen template. The implementation may define how to execute the event. The attribute "name" defines the name of the event and the PCDATA section provides the name of the function to be called.
Example: event "submit" with pcdata "foo" will send the form data (using the POST method) to the server application at the URL "?action=foo", when the user submits the form. The implementation must define, what "submit" actually means for the given form template.
ELEMENT function (description?, implementation+) ATTRIBUTE name string title string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name of the function. It should be lowercased and a valid XML and SQL identifier. |
title | string | - | - | The title is a label text that should be displayed in the documentation of this object. |
A user defined function with the given name. The function may have at most one implementation for each supported DBMS.
ELEMENT implementation (param*, return?, code) ATTRIBUTE dbms string language string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
language | string | - | - | The target programming language. This applies only to DBMS, that support more then one language. |
The implementation defines the function signature and source code. The number of parameters must be the same for each DBMS (and implementation tag).
If the attribute dbms is set to "generic", the XDDL-implementation may decide if the generic function definition is compatible with a certain DBMS. If it decides that the definition is not compatible, it may try to simulate the function. In that case the implementation may define the required syntax for the values of it's child elements "code", "param" and "return". If the definition does not match the required syntax, the XDDL-implementation may throw an error.
Note that the attribute "language" is DBMS-dependent. Most DBMS should at least support the value "SQL" as a language, while others may also include "Java", "C++" or more.
If no language is given, the default language is used. Note that this implementation may not check if the given language is really supported by the given DBMS.
For simulated functions the language attribute must be blank.
ELEMENT param EMPTY ATTRIBUTE name string type string mode string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | unique parameter name |
type | string | yes | - | DBMS-dependent data type |
mode | string | - | in | The attribute "mode" identifies the element as
|
A parameter definition as part of the function signature. The attribute "mode" identifies the behavior of the parameter. The type of the parameter is DBMS-dependent.
Example:
<function name="foo"> <implementation> <param name="p1" type="string" mode="in"/> <param name="p2" type="float" mode="inout"/> <param name="p3" type="int" mode="out"/> <return>int</return> <code>...</code> </implementation> </function> May be interpreted as: int foo ( string $p1, float &$p2, int &$p3 = null ) { ... }
Note that some DBMS only support input parameters (call by value). In that case the attribute "mode" must have the value "in". The implementation may throw an error if an unsupported definition is found.
ELEMENT return (#PCDATA)
The data type returned by the function. This setting is DBMS-dependent.
If no return element is defined, or if it is left empty, the return type defaults to "void", which means, the function doesn't return a value. If the function needs to return more then one value, it should use output-parameters instead. Be aware that in MySQL functions without a return value are called "methods".
ELEMENT code (#PCDATA)
The function body. The syntax of the content depends on the attributes for DBMS and programming language. If the DBMS is set to "generic", then this code is executed by the server application. In that case the implementation may define the required syntax itself. It is recommended, to limit the code to being the name of a function, that the implementation may call. All input parameters must be redirected to that function. Note that generic functions can't be used inside SQL-statements as these are not executed by the DBMS.
The changelog element of a database definition is meant to document updates. Updates must be sorted by version number in descending order. This means, that the first element is always the most recent update.
The updates contain the type of the modification, a version number and the subject of modification. A description of the changes may be added as PCDATA.
The changelog may be used for automated database updates. The implementation may provide an application that scans the changelog and updates the database schema from a given version number to a later version. This implementation should try to execute all changes within a transaction. In case of a nonrecoverable error it should try to rollback all changes.
ELEMENT create (description?) ATTRIBUTE name string subject string version string ignoreError bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name identifier of the changed object. It should be lowercased and a valid XML and SQL identifier. |
subject | string | yes | - | table | column | index | view | sequence | trigger | constraint |
version | string | - | - | The version this log-entry applies to. |
ignoreError | bool | - | no | Used to tell the handler function, which performs the update, how to react, if this step causes an error while updating the database structure. If the value is set to "yes", the database update should continue, even if this step fails. If set to "no", the update should stop and all previous changes should be rolled back. |
Documents the creation of a new database element. The type of element is stored as attribute "subject". The element itself is identified via the attribute "name". The name must specify a unique identifier for the element. Where necessary it must specify the namespaces, within which the element's name is unique. In that case the character '.' must be used as a delimiter. The implementation may throw an error if the element is not found or if it is ambiguous. The "description" element may be used for documentation purposes and may contain any text.
ELEMENT rename (description?) ATTRIBUTE name string subject string version string ignoreError bool oldname string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name identifier of the changed object. It should be lowercased and a valid XML and SQL identifier. |
subject | string | yes | - | table | column | index | view | sequence | trigger | constraint |
version | string | - | - | The version this log-entry applies to. |
ignoreError | bool | - | no | Used to tell the handler function, which performs the update, how to react, if this step causes an error while updating the database structure. If the value is set to "yes", the database update should continue, even if this step fails. If set to "no", the update should stop and all previous changes should be rolled back. |
oldname | string | - | - | Previous name of the renamed object. Note: For columns the name must include the table ("table.column"). |
Documents renaming an existing database element. The type of element is stored as attribute "subject". The element itself is identified via the attribute "name". The previous name of the element is stored as the attribute "oldname". The name must specify a unique identifier for the element. Where necessary it must specify the namespaces, within which the element's name is unique. In that case the character '.' must be used as a delimiter. The implementation may throw an error if the element is not found or if it is ambiguous. The "description" element may be used for documentation purposes and may contain any text.
ELEMENT drop (description?) ATTRIBUTE name string subject string version string ignoreError bool
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name identifier of the changed object. It should be lowercased and a valid XML and SQL identifier. |
subject | string | yes | - | table | column | index | view | sequence | trigger | constraint |
ignoreError | bool | - | no | Used to tell the handler function, which performs the update, how to react, if this step causes an error while updating the database structure. If the value is set to "yes", the database update should continue, even if this step fails. If set to "no", the update should stop and all previous changes should be rolled back. |
version | string | - | - | The version this log-entry applies to. |
Documents that an existing database element has been removed. The type of element is stored as attribute "subject". The element itself is identified via the attribute "name". The name must specify a unique identifier for the element. Where necessary it must specify the namespaces, within which the element's name is unique. In that case the character '.' must be used as a delimiter. The implementation may throw an error if the element is not found or if it is ambiguous. The "description" element may be used for documentation purposes and may contain any text.
A typical situation where you might want to set the attribute "ignoreError" to "yes" is when dropping an element. Usually a database update should not fail, just because an element can't be drop because it doesn't exist (anymore).
Note that some DBMS are unable to rollback dropped database elements. In case of an error this restriction may have influence on the behavior of an implementation that updates the database structure.
ELEMENT update (description?) ATTRIBUTE name string subject string version string ignoreError bool property string value string oldvalue string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | yes | - | Name identifier of the changed object. It should be lowercased and a valid XML and SQL identifier. |
subject | string | yes | - | table | column | index | view | sequence | trigger | constraint |
version | string | - | - | The version this log-entry applies to. |
ignoreError | bool | - | no | Used to tell the handler function, which performs the update, how to react, if this step causes an error while updating the database structure. If the value is set to "yes", the database update should continue, even if this step fails. If set to "no", the update should stop and all previous changes should be rolled back. |
property | string | - | - | Specifies which property of the object has been updated. |
value | string | - | - | New value of the updated property. The syntax of this attribute may be defined by the implementation. It may be a serialized string. |
oldvalue | string | - | - | Old value of the property that has changed. The syntax of this attribute may be defined by the implementation. It may be a serialized string. |
Documents the alteration of an existing database element. The type of element is stored as attribute "subject". The element itself is identified via the attribute "name". The name must specify a unique identifier for the element. Where necessary it must specify the namespaces, within which the element's name is unique. In that case the character '.' must be used as a delimiter. The implementation may throw an error if the element is not found or if it is ambiguous. The "description" element may be used for documentation purposes and may contain any text. Where available the modified "property" plus it's "oldvalue" and new "value" may be defined.
ELEMENT sql (description?, code) ATTRIBUTE version string ignoreError bool dbms string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
version | string | - | - | The version this log-entry applies to. |
ignoreError | bool | - | no | Used to tell the handler function, which performs the update, how to react, if this step causes an error while updating the database structure. If the value is set to "yes", the database update should continue, even if this step fails. If set to "no", the update should stop and all previous changes should be rolled back. |
Documents a custom SQL statement that is to be executed. It's execution may be limited to a certain DBMS. SQL-statements may be provided for any additional action, which is not covered by the other elements of this definition. For example, to copy data to a newly created table. The "description" element may be used for documentation purposes and may contain any text. The "code" element must contain the SQL code.
ELEMENT code (#PCDATA)
Contains the SQL code which is to be executed. The syntax of the content depends on the parameter "dbms".
ELEMENT change (description?, logparam*) ATTRIBUTE version string ignoreError bool type string dbms string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
version | string | - | - | The version this log-entry applies to. |
ignoreError | bool | - | no | Used to tell the handler function, which performs the update, how to react, if this step causes an error while updating the database structure. If the value is set to "yes", the database update should continue, even if this step fails. If set to "no", the update should stop and all previous changes should be rolled back. |
dbms | string | - | generic | The name of the target DBMS. The value "generic" means that the definition is suitable for any DBMS. Usually this is used as a fall-back option for DBMS you haven't thought of when creating the database structure or for those that simply doesn't have the feature in question. |
type | string | - | default | A text that unambiguously identifies the type of change to the database structure. |
This is a generic element to add user-defined types of database changes. An implementation to execute these changes must allow the definition of a function or method to handle the element for each available type. The function or method must be given the "logparam" elements as parameters in the order of their definition. It's execution may be limited to a certain DBMS. The "description" element may be used for documentation purposes and may contain any text.
ELEMENT logparam (#PCDATA) ATTRIBUTE name string
Attribute | Type | Mandatory | Default | Description |
---|---|---|---|---|
name | string | - | - | Name of this parameter. |
A parameter which is passed to the called function. The PCDATA section contains the value of the parameter. Optionally a name may be given. There must not be two parameters with the same name.
The name "SML" is an abbreviation of "Simple Markup Language". The SML format was delevoped 2001 through 2003 with the aim to create an easy to read and syntactically clean presentation, which would be acceptable to the masses.
SML is equally a relative to "JSON" ("JavaScript Object Notation") and "XML" ("Extensible Markup Language"). All constructs, which can be expressed in JSON may as well be expressed in SML format, and vice versa. It is nevertheless important to stress that SML was developed independent from JSON. Both dialects have no common historical intersections.
In simple words, the semantics used SML is comparable with that of JSON, while a the syntax is comparable to that of XML. It thus combines the widely accepted use of tags with intuitive semantics.
The task of the file format is to store non-recursive data fields in the form of a sequential file. These data fields can be either numeric or associative, and use any scalar values or other data fields, if they meet the same requirements. References are not allowed. Objects are - analogous to JSON - represented by their public object variables in the form of an associative array data. Included non-scalar values are treated recursively.
The purpose of this file format is to store parameters to initialize components of the framework. The purpose is not to store larger amounts of data in this format. In particular, the SML format is not designed as a replacement for XML.
Infinite recursions are not recognized.
Note: The test, whether a structure contains infinite recursions or not, is not trivial. The usual approach is a path tracking procedure. However, this produces an overhead of T = O(n) steps, where n is the length of the path. The proof for this lower bound is trivial. In order to avoid such an overhead it might be considered to run this test only every x steps, where an appropriate value is to be chosen for the positive integer x with x> 1. Despite this potential optimization, quadratic running time can be expected, for the whole program, including the path checks. This is not acceptable for many applications. For reasons of performance it was decided to refrain from this type of check.
At this point I would like to note that the naive approach, to count the number of elements to check whether the number of iterations exceeds the number of elements, is not practical. In order to "count" all elements a complete traversion would be carried out. If, however, the structure did contain an infinite recursive path, the program would never terminate and the result would be undefined.
The task to provide files for initialization or configuration of a framework is not totally trivial. Since the framework may have multiple plug-ins, whose configuration options should be stored in the same files as the rest of the configuration. The numbers, structure, type and names of these files is however unknown at the time of development. Eventually the plug-ins, the framework will work with, do not even exist at this point. To be still able to provide a practical DTD or schema in advance, with respect to the these restrictions, the logical solution would be to retreat to the natural properties of the used variables. Stable properties, like the data type of a variable, may be used to create a structure, which is predictable, because it is dictated by syntactical properties of PHP.
Because PHP is not strictly typed and references and objects may not be part of this configuration by definition, this relaxes the task to scalar variables and arrays (non-recursive and recursive data structures). This two kinds of nodes are defined. The Tag "scalar" is introduced to represent scalar values. It may only contain CDATA section ? this matches a scalar context ? but no other tags or elements. The Tag "array" is introduced to represent array values. It may not contain CDATA sections, but tags only. These tags represent the items of the array. Since XML explicitely expects a single root element the tag "root" is introduced. No further tags are required.
Take a look at the following example, which represents two data fields.
<?xml version="1.0" ?>
<!DOCTYPE example SYSTEM "example.dtd">
<root>
<array name="a">
<array name="0">
<scalar name="a"><![CDATA[value]]></scalar>
<scalar name="b"><![CDATA[value]]></scalar>
<scalar name="c"><![CDATA[value]]></scalar>
</array>
<array name="1">
<scalar name="a"><![CDATA[value]]></scalar>
<scalar name="c"><![CDATA[value]]></scalar>
</array>
</array>
<array name="b">
<array name="0">
<scalar name="q"><![CDATA[value]]></scalar>
<scalar name="r" />
<scalar name="s"><![CDATA[value]]></scalar>
<scalar name="t"><![CDATA[value]]></scalar>
</array>
</array>
</root>
By this example some characteristics are noticeable. First of all the element "root" is complete without semantic meaning. It exists only, because the syntax requires it.
The empty tags may also be considered to be somewhat atificial constructs. They are included, but it is not completely clear, how to interpret these tags regarding to the variables they represents. PHP expects no definition of variables prior to their first use. So an initialization of a non-existing variable with an "undefined" value, even with the constant "null", doesn't make much sense. Particularly since in a configuration file a "null" value has no semantic value at all. Therefor it will be defined by convention, that empty tags should be avoided. Any access to empty or nonexistent tags will be converted to the boolean value "false", as implied by common PHP conventions.
Furthermore, it is foreseeable that this representation could be confusing if used to store larger amounts of data. Ffor example, in the event that a data field contains50 or more items, which may also be nested and may contain large CDATA sections with a number of line breaks, it is likely that the readability would be affected. For reasons of readability it would be an advantage, if the viewer could tell by the end-tag, which item is closed.
The separate marking of CDATA sections could also affect readability, especially since the sense of this syntax may not be obvious to an inexperienced layman. In addition, the previously made definition explicitly demands nothing other than a CDATA section may be used at this anyway, which is why no real extra value is introduced by this syntax. There is a very exceptional case, namely that the CDATA section contains another closing "scalar" tag. However, this case may be easily avoided by the use of entities, which causes the whole syntax to be obsolete.
Next the focus is to be set on the tags themself. For example, the text "<array name="b">". This text may seem to be unnecessarily complex to a layman. Since it is already obvious by it's structure this element has to be an array anyway. The tag "scalar" may not even be used at this point, since it may not contain other tags but just CDATA. It is of no extra value to stress that this is an (empty) array. The attribute "name" is common to all tags. This derives directly from syntaktik properties of PHP, since the attribute represents the identifier of a variable in PHP. So it would be more intuitiv to use this identifiers instead of the more generic names "array" and "scalar" to name the tags. The same argument could be used againts the tag "scalar".
In respect to what has been discussed so far the presentation could be simplified.
<a>
<0>
<a>value</a>
<b>value</b>
<c>value</c>
</0>
<1>
<a>value</a>
<c>value</c>
</1>
</a>
<b>
<0>
<q>value</q>
<s>value</s>
<t>value</t>
</0>
</b>
Even without extensive explanation is apparent that both variants represent the same content. Similarly, it should probably not be necessary to discuss which of the two syntaxes are more intuitive to read.
But: because of it's syntactic properties, this variant is not a well-formed XML document. For example, there is no single root element and also "0" is not even a valid name for a tag in XML. It was therefore necessary to choose a different name. This name is "SML" for "simple markup language", in allusion to "XML" ("eXtensible markup language").
SML demands some syntactic properties which differ from XML. First, it should be mentioned that encoding a file in UTF-8 or UTF-16, as common in XML, unfortunately still may cause problems with many languages when reading these files. Among these languages are at the present time, inter alia, Perl 5 and regrettably PHP 4. While this might be seen as a failure of the developers and not a property of XML as such, it is still an unacceptable condition, when it comes to initialize an application. Therefore, it is useful for the purpose of the initialization of the framework not to use these encodings (at least for the current moment). Instead ISO Latin-1 is used as default charset. Characters, which are not included in this charset, should be encoded by using proper entities.
Unlike XML the SML syntax does not demand, that a document must contain exactly one single tag as root element. If a document contains a forest of multiple node trees, they are interpreted as child elements of a virtual anonymous root element.
Furthermore the SML format demands that all opened tags need to have a corresponding closing tag. The names of these tags are by definition not case-sensitive.
There is a convention, that a line break has to be entered after each closing tag. Also by convention, only white-space characters are allowed prior to an opening tag. A tag may either contain character data (CDATA) or other tags, but not both. If the tag is a CDATA-section, both start and end tags need to be in one line. If the tag contains other tags, a line break must be inserted directly after the opening tag. Line breaks in CDATA-section may be inserted by using escape sequences like "\n".
All CDATA-section outside a tag and all section which do not comply with this syntax are ignored and treated like comments. Unlike XML, no special handling for CDATA-sections or comments is demanded.
It is not allowed to use the XML-like syntax for empty tags "<br />" (use "<br></br>" instead).
Finally, the SML format uses no attributes by definition.
The reason for these rather restrictive syntax will become clearer with the following example.
The following examples will be described, how native data types in PHP scripts are represented by the SML format.
Presentation in PHP-code:
<?php
$a = 1;
$b = 'string';
$c = 12.5;
$d = true;
?>
equivalent presentation in SML:
<a>1</a>
<b>string</b>
<c>12.5</c>
<d>true</d>
At this point, it is important to understand a syntactic constraint of PHP. A PHP function may have only one single return value. (The case that the function takes it's parameter by reference is not considered here.) A PHP function the reads the contents of a SML document, is thus forced to return the (multi-dimensional) content as an array. The contents of this array can be copied to variables within the context of the calling program fragment, so that the initial condition is restored.
To a second example: Documents in JSO-notation are subject to the same restrictions.
Presentation in PHP-code:
<?php
$A = array();
$A[0] = 1;
$A[1] = 'string';
$A[2] = 12.5;
$A[1000] = true;
$B = array();
$B[0] = 2;
?>
equivalent presentation in SML:
<A>
<0>1</0>
<1>string</1>
<2>12.5</2>
<1000>true</1000>
</A>
<B>
<0>2</0>
</B>
Presentation in PHP-code:
<?php
$SCREEN = array();
$SCREEN["width"] = 1024;
$SCREEN["height"] = 768;
$SCREEN["depth"] = "32-Bit";
?>
equivalent presentation in SML:
<SCREEN>
<width>1024</width>
<height>768</height>
<depth>32-Bit</depth>
</SCREEN>
Presentation in PHP-Code:
<?php
$A = array();
$A["a"] = 1;
$A["b"] = 2;
$A[0] = "three";
$B = array();
$B[0] = true;
$B["a"] = 1000;
$B["b"] = array();
$B["b"][0] = 1;
$B["b"][1] = 2;
$B["b"]["a"] = 3;
?>
equivalent presentation in SML:
<A>
<a>1</a>
<b>2</b>
<0>three</0>
</A>
<B>
<a>1000</a>
<b>
<0>1</0>
<1>2</1>
<a>3</a>
</b>
</B>
As can be seen from the above examples, the format is well suited to store scalar variables and data fields, which are used as parameters for the initialization of the framework. The specification of a data type is not necessary, because PHP is not strictly typed. The type of a variable is chosen dynamically at runtime. The only essential distinction is between scalar values, and data fields. This distinction can be easily made based on the syntax.
There are a number of popular alternatives to the SML format used in this framework. The following section describes some of these alternatives, compares and discusses the results.
Below several aspects will be discussed:
Since it can be predicted, that some users may want to edit configuration files by hand, the readability of their source code is crutial. The syntax should be intuitive. This is particularly important, since at the present time a graphical interface can only be provided for a subset of the configuration settings of the framework. Given the wealth of options, it seems unlikely that a full coverage can be achieved already in the near future. Also it is questionable, wether this is desirable. Finally, it should also be taken into account that in principle experienced users may decide deliberately and intentionally to ignore the functions of a "cumbersome" GUI, because they feel like it is slowing them down.
An interesting factor is the performance, because the task to initializate the framework may be time critical. As the files are read on each call of the framework, the process of reading must be efficient. While it does not always make sense to try to compare the "speed" of different implementations, it should not be avoided where it is possible.
Also it might be usefull to take a look at the limitations of a file format. If there are K.O. factors for a certain format under certain precondition, this should be mentioned.
One of the powers of XML is it's portability to other programming languages. Parser for XML are integrated in nearly all modern dialects. However, this is no extra value when the data meant to initialize a framework, which is only available in a special programming language.
The current tool of choice for handling XML files, which is available in both versions 4 and 5 of PHP is the native XML parser. However, this XML parser has a weakness. It may only read, but not write, XML files. A standard component for writing XML files is available with PHP version 5.
Also it is to be mentioned that to get an array from a XML file using the XML parser wouldn't require less effort than it was to write the whole script to read SML files. It also comes with the risk that the implementation of the XML parser might be subject to change in a later version of the language (as any other third-party implementation) even if this is unlikely to happen. In addition there are no real advantages of this implementation, other than the fact, that XML is a popular dialect and widely accepted.
Let us shed some light on the performance. However, this is anything but straightforward and all results might as well be subject to discussions, as they are dependent on the tool used. There are many of the them for the XML format, even if you restrict your search to PHP.
In the following scenario, a simple XML document is to be loaded and put into a data field.
Here isthe source code used for the XML document.
<?xml version="1.0"?>
<root>
<array name="channel">
<scalar name="title">Test</scalar>
<scalar name="link">about:blank</scalar>
<scalar name="description" />
<scalar name="language">de-de</scalar>
<array name="0">
<scalar name="title">test item 1</scalar>
<scalar name="pubDate">Do, 9 Jun 2005 13:23:18 +0200</scalar>
<scalar name="description"><![CDATA[test item]]></scalar>
<scalar name="link" />
<scalar name="author">test author</scalar>
</array>
<array name="1">
<scalar name="title">test item 2</scalar>
<scalar name="pubDate">Do, 9 Jun 2005 13:23:53 +0200</scalar>
<scalar name="description"><![CDATA[test item]]></scalar>
<scalar name="link" />
<scalar name="author">test author</scalar>
</array>
<array name="2">
<scalar name="title">test item 3</scalar>
<scalar name="pubDate">Do, 9 Jun 2005 13:24:18 +0200</scalar>
<scalar name="description"><![CDATA[test item]]></scalar>
<scalar name="link" />
<scalar name="author">test author</scalar>
</array>
<array name="3">
<scalar name="title">Linktest</scalar>
<scalar name="pubDate">Do, 9 Jun 2005 13:24:36 +0200</scalar>
<scalar name="description"><![CDATA[test item]]></scalar>
<scalar name="link">about:blank</scalar>
<scalar name="author">test author</scalar>
</array>
</array>
</root>
Now the same in SML format.
<channel>
<title>Test</title>
<link>about:blank</link>
<language>de-de</language>
<0>
<title>test item 1</title>
<pubDate>Do, 9 Jun 2005 13:23:18 +0200</pubDate>
<description>test item</description>
<author>test author</author>
</0>
<1>
<title>test item 2</title>
<pubDate>Do, 9 Jun 2005 13:23:53 +0200</pubDate>
<description>test item</description>
<author>test author</author>
</1>
<2>
<title>test item 3</title>
<pubDate>Do, 9 Jun 2005 13:24:18 +0200</pubDate>
<description>test item</description>
<author>test author</author>
</2>
<3>
<title>Linktest</title>
<pubDate>Do, 9 Jun 2005 13:24:36 +0200</pubDate>
<description>test item</description>
<link>about:blank</link>
<author>test author</author>
</3>
</channel> <Se
For comparison, PHP 4 with the standard XML parser will be used. Before and after each parser step the command "microtime" will be used to measure the time. The difference between these values will be given in milliseconds. It is not important to see how many milliseconds on implementation is faster than the other. Obviously you can't judge this by this kind of testing, since the procedure is far too rudimentary to produce representative results.
The configuration of the test system can be neglected in this case, since only the magnitude of the difference between the two techniques should be considered.
It's even more problematic, that hand-write PHP-Code is needed to have the XML parser handle the tags. Therefor it might be possible that less performant PHP code could have an impact on the results. To avoid this the XML parser will be measured two times: first with and second without the implementation.
This test does not aim to vote pro or contra the use of this XML parser. The aim of the tests is therefore not a vote for or against the use of the XML parser. Much more it should show whether there are clear differences between the two versions. In particular, whether the SML variant may not be suitable for the framework, because of poor perfomance.
For this modest task this simple test should provide sufficiently accurate results.
XML-Parser 1 | XML-Parser 2 | SML-Script | Difference SML to XML 1 |
Difference SML to XML 2 |
---|---|---|---|---|
0,003685s | 0,001813s | 0,001626s | -0,001153s (31%) | -0,000187s (10%) |
0,003410s | 0,001666s | 0,001255s | -0,002155s (63%) | -0,000411s (24%) |
0,003367s | 0,001629s | 0,001312s | -0,002055s (61%) | -0,000317s (19%) |
0,003394s | 0,001614s | 0,001240s | -0,002154s (63%) | -0,000374s (23%) |
0,003381s | 0,001618s | 0,001228s | -0,002153s (63%) | -0,000390s (24%) |
0,003386s | 0,001613s | 0,001400s | -0,001986s (59%) | -0,000213s (13%) |
0,003387s | 0,001620s | 0,001296s | -0,002091s (61%) | -0,000324s (20%) |
0,003658s | 0,001617s | 0,001247s | -0,002411s (65%) | -0,000370s (22%) |
0,003678s | 0,001633s | 0,001454s | -0,002224s (60%) | -0,000179s (10%) |
0,003725s | 0,001613s | 0,001573s | -0,002152s (58%) | -0,000040s (2%) |
Comparison of the performance of XML parser and SML script (excerpt)
1 XML parser when using a comparable implementation to the one of the SML script
2 XML parser running empty, without processing the input at all
Considering the above short excerpt of the results, it comes clear that the XML parser with the PHP-code included is clearly less performant than the SML script. As already mentioned, this might also be due to less efficient programming. But when considering column 2 ( "XML parser 2") compared with column 3 ( "SML script"), you can see that even in this case the SML variant comes close to the values of the XML parser. This is particularly remarkable because the XML parser in this case ran empty and produced no results at all, since no implementation was given to handle the content. Therefore, the argument that an unfavourable implementation might be slowing down the XML parser, can no longer be maintained.
This test provided no indication that the efficiency of the implementation might have a negative effect on the performance of the entire application.
The use of SimpleXML only slightly differs from that of SML, as the following source code indicates.
<?php $sml = SML::getFile("test.config"); $xml = simplexml_load_file("test.xml"); ?>
The only obvious difference is the type of the return value. While the SML variant returns an array as result, SimpleXML returns an object of type "SimpleXMLElement". One might argue which solution is better.
One argument against SimpleXML is that it uses object properties in PHP to represent names of tags and attributes. This is problematic because these may contain different characters in PHP and XML. For example, this concerns the character '-', which may not be included in the name of a variable in PHP. Consequently, at the time of preparing this document, it was very difficult to acces a tag like <foo-bar> via SimpleXML.
Initialization files, often abbreviated with the file extension "ini", are themselves a suitable alternative, unless mapping of complex structures is necessary. For example, the presentation of keys, especially in deeply nested structures, may be confusing. The format provides simple syntax. But the format is not as flexible and powerfull as XML. For example the typical tag-notation, which is currently very popular, is missing. Creating a DTD or a similar file, which describes the structure of the file format is not possible, for practical and theoretical reasons. Still this is no disadvantage. Considering that the primary task of this file format is to store any structured associative data fields taken from a script or programming language, it is clear that demanding a static structure doesn't make sense in this scenario (so does the DTD). A combination, which unites the advantages of both formats in itself, would be ideal.
The following code snippet shows an example of an initialization file.
[STORE\0]
type=book
author=Dr. E Xample
[STORE\0\TITLE]
main=Proto matter
subtitle=Alpha et Omega
[STORE\1]
type=cd
author=Barbara Singer
[STORE\1\TITLE]
main=Best-Of
Another alternative is to use JSON. The following is an example:
"STORE" : {
"0" : {
"type" : "book",
"author" : "Dr. E Xample",
"TITLE" : {
"main" : "Proto matter",
"subtitle" : "Alpha et Omega"
}
},
"1" : {
"type" : "cd",
"author" : "Barbara Singer",
"TITLE" : {
"main" : "Best-Of"
}
}
}
PHP offers the possibility, like Java, to serialize and restore variables by using the commands "serialize" and "unserialize".
The following code snippet shows an example.
a:1:{s:7:"CHANNEL";a:7:{s:5:"TITLE";s:4:"Test";s:4:"LINK";s:11:"about :blank";s:8:"LANGUAGE";s:5:"en-us";i:0;a:4:{s:5:"TITLE";s:10:"test i tem 1";s:7:"PUBDATE";s:29:"Th, 9 Jun 2005 13:23:18 +0200";s:11:"DESC RIPTION";s:8:"Test item";s:6:"AUTHOR";s:11:"Test author";}i:1;a:4:{s: 5:"TITLE";s:11:"Test item 2";s:7:"PUBDATE";s:29:"Th, 9 Jun 2005 13: 23:53 +0200";s:11:"DESCRIPTION";s:9:"Test item";s:6:"AUTHOR";s:11:" Test author";}i:2;a:4:{s:5:"TITLE";s:11:"Test item 3";s:7:"PUBDATE";s :29:"Th, 9 Jun 2005 13:24:18 +0200";s:11:"DESCRIPTION";s:8:"Test item ";s:6:"AUTHOR";s:11:"Test author";}i:3;a:5:{s:5:"TITLE";s:8:"Linktest ";s:7:"PUBDATE";s:29:"Th, 9 Jun 2005 13:24:36 +0200";s:11:"DESCRIPTIO N";s:9:"Test item";s:4:"LINK";s:11:"about:blank";s:6:"AUTHOR";s:11: "Test author";}}}
This solution is, without doubt, very performant. The command "unserialize" works app. 2 times faster than SimpleXML. However, the readability of this data stream is problematic. Obviously it is not a good solution for editing the file by hand. As good readability is essential for the Framework, this solutions was not chosen.
Unwanted advertising in e-mails, forums and guest books are a current topic. This section will explain what it is and tell some methods used by attackers. Subsequently, it will show what has been done to handle these threats.
There is a fine difference between "Spam" and "Floods".
The word "Spam", as it is used here, means the repeated and unrequested transmission of contents to a person or a group. For example, the sending of unsolicited e-mails with promotional content to third parties. Even massive, unsolicited phone calls are a form of "spam". Sending "spam messages" is illegal in many countries (including Europe and the United States) and will be prosecuted.
The site "spamhaus.org" defined "spam" as follows.
The word " Spam " as applied to Email means Unsolicited Bulk Email ("UBE").
A message is Spam only if it is both Unsolicited and Bulk.
The word " Flooding " means the deliberate, unsolicited "flooding" of a medium with content. Floods are meant to disturb the normal operation of a medium. For example massive publishing of senseless texts in a public forum. Also, a person, who disturbs a public meeting by permanent interruptions, does a form of flooding. Flooding may be illegal in some cases. For example if it causes economic damage.
In most cases "Spam" is produced to publish advertisements. While "Floods" are at most a form of vandalism.
Spam in forums and guest books are an increasing problem. This also applies to web logs an wikis. Often, these records are quickly identified and then removed, but the removal of unwanted entries of this kind takes time and money. The following section describes what an attacker could do and ways to react to it.
The attacker might do as follows. First, the attacker might looks for a possibly vulnerable script like a guestbook or forum. He examines the forms that are used to create new entries, and notes the names of the required form fields. The attacker search for specific texts within the targeted application. Using these texts as search terms, the attacker identifies web sites where the script is installed.
The attacker creates a list of URLs for the pages and stores the list in a file. With the help of a short program, the messages are published to all forms on the list.
If the attacker is lucky enough, search engines like Google index these pages before the administrator removes the ad. This way the embedded hyperlinks also increase the PageRank of the so-advertised web sites.
There are multiple ways to react to such attacks.
The easiest way is to simply change the URL. Usually, the scripts of most attackers work asynchronously. Therefore usually he will not recognize so soon that the targeted site is no longer available. You may expect that the attacker might either wait for the search engine to refresh the URL, which might easily take up 8 weeks, or even longer, until he re-visits the site and extracts the new link manually. In both cases, the victim should temporarily be safe.
For the Framework changing the URL is easy and can be done without any difficulties. It is so simple, because the Framework can handle multiple instances, each identified by a unique id, as "configuration profiles" . Which profiles are active and which are not, is set by the administrator.
Thus, to change the URL of an application you only have to rename the profile and correct the links. This is a question of just a few minutes. The positive side effect consists of the fact that the aggressor does not receive an error 404 (side not found) during synchronous transmission, or with examination of the URL over an automatic program from the server. Even by manual examination he does not notice that the profile is inactive. Depending on the configuration of the framework the attacker will continue to submit messages to the inactive forms and (unless the profile is write-protected) write entries.
By personal experience I can report that aggressor may be lead astray for months without noticing their error.
A further measure is to block IPs or IP-ranges. That is above all useful if the aggressor always uses the same proxies for the attacks. Unfortunately this is not often the case. Nonetheless, this possibility was considered and an included as an optional feature for the Framework. This function can also be used for applications that run within a local network, to prevent unauthorized access from the Internet.
In order to prevent from the start that an aggressor can write entries using an automatic program, you may add CAPTCHAs to threatened forms. In order to be able to write an entry, the visitor must type in the shown code. While this is an easy task for a human, it is a lot more difficult for an automatic program.
There are problems that arise from this for the attacker. The program needs to retrieve the code from the server ? even if the code is successfully extracted it requires valueable time. An asynchronous communication is not longer possible: since the request has to be send and the code retrieved. There is also the risk that the attacker's program is slowed down by an intentionally modified server. Also it will cause high effort for the attacker.
It is to be assumed that an aggressor will avoid such protected web applications.
The Framework implements such a check. The standard library provides the actions "security_get_image" and "security_check_image", that do produce the required graphics in PNG format using the PHP GD library and that do maintain a code table to check the user input. This table is hidden from the public via ".htaccess" and is refreshed automatically.
Flooding can be both, unintentional and intended.
This may happen unintentionally if the server temporarily experiences high traffic. In this case it might occur that a user has to wait a longer time for the server's response. Impatience may cause that he refreshes the page and resends the request, which will create a second identical entry. Even if the web site itself gets low traffic, this may still occur, if the web site is hosted on a "shared server". This is when the web site where the framwork is installed is shared with other customers. It is not uncommon, that 1000 or even 10000 customers on one single server have to share the same resources.
Flooding by intention is often a kind of "vandalism". Goal of the aggressor is it to disturb and/or to hinder the normal use of the targeted media.
Whether intended or accidental - flooding can be reduced by automated measures.
Two methods are to be explained, which attackers could try to disturb the operation of the application. First: by entering extremly long character strings without line breaks, lining an excessive chain of line breaks, writing extremly long (senseless) texts in general, or extra large graphics, where all of these contents may break the layout of application. This may cause that parts of the graphical layout are no longer shown as intended by the author of the web site. In addition it could force other users to scroll the page, and the presentation of contributions by other authors may thereby be impaired.
The second method is mass-submission of irrelevant contributions. This will make it difficult for other users, to recognize the important messages and notice their contents. For this attack, the aggressor sends several identical or similar contributions to the server.
For the second method, the Framework provides the following solution. When two successive contributions are send, it checks whether the contributions have the same content. If so, the second entry is rejected. This esp. prevents accidental flooding, but also makes attacks in general a little more difficult.
Additionally, there is an option, which allows to restrict the number of consecutive entries written by a user to a certain number. The user is identified by the IP. If the option is active and the user writes more entries than expected the user will be denied further contributions until another user writes an entry.
For the avoidance of the first method the framework provides a number of filters. These filters restrict the length of text, limit the proportions of graphics to maximum values, remove conspicuous repetitions and force line breaks, when a string is more than 80 long.
These methods make the disturbance of the normal operation more difficult, yet they can't offer 100% protection. This would not be desirable anyway. This is due to a well-known dilemma. If one narrows the effective region of the filters, then this reduces the number of the false alarms ("false-positives"). These are those submission which are recognized as flooding-attempts, but in fact are none. At the same time however also a higher number of real attacks will slip by the net of the filters. Conversely: if one expands the target area of the filters, the will prevent a larger number of attacks. However, this will also increase the number of "false-positives" accordingly. Ultimately, it is thus always a "trade-off" between adequate security and reasonable tolerance, to prevent false alarms.
In order to increase the security of passwords it is reasonable to encrypt these. One-way encryption algorithms [ I ] are well suited for this. In this section two of the most important representatives are explained. In PHP they are implemented by the functions "md5()" and/or "crypt()".
The DES-procedure [ II ] was for many years the standard encryption algorithm in the US. With the increasing power of available hardware on the market, the simple DES-algorithm is no longer safe. The multi-encryption variant "Triple-DES" may be considered a more safe modification. As follow-up algorithm for DES the US-american National Institute of Standards selected the so-called RIJNDAEL algorithm AES [ III ].
DES works on the binary representation of the input text. It produces an initial permutation of this binary notation. Subsequently, a block cipher is used for encryption. The DES-algorithm is a Feistel cipher. Feistel ciphers do apply an internal block cipher multiple times on parts of the message, to create a number of round keys.
For the safety of Feistel ciphers, the safety of the used internal block cipher is of crucial importance. Currently DES may be considered safe enough for most of your everyday-applications and the algorithm is still used in many environments. For example DES is used on Unix systems for the authentication of users.
The MD5-algorithm [IV] is a hashing algorithm. Important for the security of a hash process is the property of the collision freedom. A collision is when two different clear texts produce the same cipher text. If a collision is found, it is possible to calculate the plaintext with the help of appropriate procedures. It is known, that MD 5 is not free of collisions. However, it is unknown whether there any compression functions, which have no collisions. Therefore, it is acceptable in practice, if it is sufficiently difficult to find a collision. Such procedures are considered "collision resistent". Equally important is the speed of the algorithm because the encryption itself should be - in order to be suitable for authentication - most efficient.
Name of procedure | Block length in bits | Speed percent |
---|---|---|
MD4 | 128 | 100 |
MD5 | 128 | 68 |
RIPEMD-128 | 128 | 39 |
RIPEMD-160 | 160 | 24 |
Comparing the performance of different authentication procedures
The security of MD5 is now, however, in doubt [V]. The Heise Verlag first reported in early 2005 that researchers apparently succeeded in creating a faster procedure for the calculation of MD5 hashes [VI] to find collisions. A short time later, the message appeared that it has been able to create different certificates with the same MD5 hash [VII]. Only slightly more than half a year [VIII] a company offered a paid service, to reverse MD5 and SHA1 hashes by using pre-calculated tables, as long as the fee is right. It is therefore only a matter of time before MD5 won't provide enough security for sensitive applications anymore.
Also the safer algorithm SHA-1 [IX], with a bit length of 160-bit as opposed to MD5 with 128-bit, is already broken. As reported by Schneier [X], a group of Chinese cryptologists (Xiaoyun Wang, Yiqun Lisa Yin, Hongbo Yu), from the Shandong University succeeded in finding a collision in SHA-0 and SHA-1. For SHA-0 within 2 39 steps of operation, for SHA-1 it was 2 69 steps. This is much faster than the brute-force approach, with about 2 80 required steps of operation.
The framework also uses MD5 to encrypt passwords. In addition, SHA-1 is used to generate unique session IDs. A change of password encryption from MD5 to SHA-1 will be considered in due time.
Thomas Meyer, www.yanaframework.net