Thursday, December 30, 2010

MAC Apache MySQL PHP

MAMP in short, is what most php developer is looking for on MAC.
First, get macport installed.
Then, follow this instruction here:

Make sure to disable the web sharing of MAC, which is default enabled.
And to start it, you will need to to run the daemons in /opt/local/etc/LaunchDaemons.
Example:
/opt/local/etc/LaunchDaemons/org.macports.apache2/apache2.wrapper start

DocumentRoot is located @ /opt/local/apache2/htdocs
Configuration @ /opt/local/apache2/conf


MAC and repository

MAC was originated from Darwin BSD, a varient of unix made by open source community.
But Apple did a well job, making the user interface suitable for desktop use.

Alike linux such as ubuntu, it has a *nix like file structure.
Most installer packages are in extension .dmg or .pkg.
Or you may install things from source code itself.

There are some way to make repository like installer, alike apt-get or yum in linux.
One of it is macports, macports.org
Installing MacPorts requires XCode to be installed. It can be found on apple website.
XCode with the iOS sdk is pretty huge, total up to 3+Gb.

Mac paths is different from most linux.
The application is mostly installed under /Application.
While the user home directory is under /Users/your_user_name
Other than that, its pretty much the same file structure with linux.

Keyboard wise, its pretty much confusing. There are no direct home or end key.
The function key needs the Fn function to press together to make it work.
But you may change this setting in the System preference, found on the upper left with a small Apple icon.
As for home button, pressing the "Command" + "Left arrow" gives you the home, while "Command" + "Right arrow" gives you the End.
The "Command" key is pretty much alike the control key in windows or linux.
But in terminal, the Control+c of linux have to be "Control"+c in mac.

What is really awesome in MAC is their touch pad. Its multi touch, and it detect 4 fingers!
Using Four finger and dragging down, give the overview of all the tasks in thumbnail.
Using 3 fingers and dragging left gives the "Go back", while dragging right gives the "Go Forward" maneuver. Touching the pad with 2 fingers give the "Right" click trigger. And as usual, using 2 fingers to drag will give the scrolling maneuver, alike the iphone or ipad.
Multi tasking in MAC is pretty much more smoother compared to ubuntu 10.10. But there is a new patch done in the recent kernel hacking which seems to solve this issue on X Windows, and hopefully this will give the more consistant multi-tasking feel on linux.

More on MAC keyboard:

Hope this give you a head start of understand MAC.
Cheers~ :)

Wednesday, December 29, 2010

jquery.form onsubmit

jQuery has a plugin for form which does alike mootool .load method for form..
If you need to submit the form through javascript, you may not use
document.formname.submit(), as this will bypass the onsubmit event.

To make it work, use $("#formid").submit();


Reference:

Monday, December 27, 2010

External MX record / Mail server

It is possible to set external mail on server by setting MX record of the server dns to point to external IP. Another method will be removing the dns of the domain from the server and let the DNS be resolved on the external managed DNS.

If you have Postfix configured for your smtp, you will need to set the virtual-transport of the domain to another place.
Example:
domainname: exmaple.com, transport: smtp:ASPMX.L.GOOGLE.com:25

If you are running virtual domain support with mysql for postfix, you may find the configuration in /etc/postfix/mysql-virtual_transports.cf , stating the table in mysql to change.

More info:

Thursday, December 23, 2010

Debugging PHP with XDebug and Netbeans

On ubuntu:
sudo apt-get install php5-xdebug

Edit xdebug.ini in /etc/php5/conf.d/xdebug.ini
#Add this line:
xdebug.remote_enable=on

Restart apache:
sudo /etc/init.d/apache2 restart

In firefox, add extension "easy XDebug".
Restart firefox.

In netbeans, right click on project, "properties".
Go to "Run configuration", "Advanced"
Check on "Dot not open browser", "okay".
Click on "Debug", "Debug project"

Go to firefox, click on "Start XDebug session" icon, then open the URL to your local project.
Example: http://localhost/myprojectnamefolder/

You are done, you will notice debugging buttons on the top toolbar, on the right.
You may continue / step in / other action.





MODx 2.0.6 update

MODx team have finally made it right :)
Every logged in session now have to be in
$_SESSION["modx.user.contextTokens"]

Example:
$_SESSION["modx.user.contextTokens"]["web"]=UserId#


And $modx->executeProcessor($options) now get $scriptProperties from $options, no longer $_POST.



Wednesday, December 22, 2010

MODx 2 Plugin OnBeforeDocFormSave

Here is an example to make a tv to be a required field:
----------------------
<?php
/*
* $mode: "new/upd"
* $resource: modResource object
* $id: resource id
* Calling $modx->event->output("..."); will output error and cancel save
*/


if (empty($_REQUEST["tv4"])) {
$modx->event->output("Field 'XXX' is required");
}


Tuesday, December 21, 2010

MODx 2 Resource Create/Update Cancel redirect

If you need to create your own resource grid,
you may find it difficult to control the resource create / update redirect on after save or when cancel button is clicked.
One way to do this is to add your own startupclientscript like this:
----------------------
setCancelButton();

function setCancelButton() {
if (!Ext.getCmp("modx-action-buttons")) {
setTimeout("setCancelButton()", 1000);
return;
}
Ext.getCmp("modx-action-buttons").actions.cancel = 90;//your action number
for(var c=0; c Ext.getCmp("modx-action-buttons").items.items.length; c++) {
if (Ext.getCmp("modx-action-buttons").items.items[c].process == "cancel") {
Ext.getCmp("modx-action-buttons").items.items[c].handler = cancelHandler;
}
}
}

var cancelHandler = function (btn,e) {
var fp = Ext.getCmp(this.config.formpanel);
if (fp && fp.isDirty()) {
Ext.Msg.confirm(_('warning'),_('resource_cancel_dirty_confirm'),function(e) {
if (e == 'yes') {
MODx.releaseLock(MODx.request.id);
MODx.sleep(400);
location.href = '?a=90'; //your url or action here
}
},this);
} else {
MODx.releaseLock(MODx.request.id);
location.href = '?a=90'; //your url or action here
}
}


Sunday, December 19, 2010

DHTMLX Grid reloading data

Handy function to reload data in grid

mygrid.reloadPage = function() {
this.load(this.xmlFileUrl + getUrlSymbol(this.xmlFileUrl) + "posStart=" +
((this.currentPage-1)*this.rowsBufferOutSize) + "&count=" + this.rowsBufferOutSize, this._data_type);
}

DHTMLX Grid attachHeader and attachFooter

DHTMLX Grid is very strict on height setting.
If you ever attachHeader or attachFooter, always remember to set the height of the column you are adding using style="height:30px".

After setting, running:
grid.setSizes();
grid.callEvent("onGridReconstructed", []);

This above also applies to subgrid

Saturday, December 18, 2010

DHTMLXGrid subgrid paging

To create paging in subgrid, one must create a row in the table.
The example below put up paging as footer.
The enablePaging should only be call when loading data.
This is because the grid is not created yet, therefore the div did not exists yet.


mygrid.attachEvent("onSubGridCreated",function(subgrid, id){
//collapse all other subgrid
subgrid.setHeader("#master_checkbox,Id,Name,Age");
subgrid.setColTypes("ch,ro,ro,ro");
subgrid.setInitWidths("40,50,100,100");
subgrid.init();
subgrid.enableAutoHeight(true);
//notice the div is given the unique id
//the trailing #cspan to set colspan of the other column to merge with 1st column
// therefore, the #cspan have to match your column count-1
subgrid.attachFooter("<div id="subgrid_h" + id + "" style="width:100%;"></div><div id="subgrid_f" + siteid + "" style="width:100%; height: 20px;"></div>,#cspan,#cspan,#cspan");

subgrid.load("http://example.com/path_tojson?count=10&posStart=0&siteid=" + siteid,function() {
subgrid.enablePaging(true, 10, 10, "subgrid_h" + siteid, true, "subgrid_f" + siteid);
subgrid.setPagingSkin("bricks");
subgrid.setSizes();
subgrid.callEvent("onGridReconstructed", []);
}, "json");

return false; // block default behavior
});

DHTMLX Grid subgrid

Example of Grid with subgrid support with JSON data:

//=====Main grid======//
mygrid = new dhtmlXGridObject('divGridForm');
mygrid.setImagePath("imgs/");
//setting header row, notice 2nd column is a "check-all" checkbox
mygrid.setHeader(["","#master_checkbox","id","name");
mygrid.setInitWidths("30,40,50,100);
mygrid.setColAlign("center,center,left,right");
//expander symbol, checkbox, read-only field,...
mygrid.setColTypes("sub_row_grid,ch,ro,ro");
//setting sortable columns, notice 1st 2 columns is empty to disable sorting
mygrid.setColSorting(",,str,str");
mygrid.enablePaging(true, iLimit, 10, "pagingArea", true, "recinfoArea");
mygrid.setPagingSkin("bricks");
mygrid.load("http://example.com/pathtojson", "json");

//Main grid row double clickevent
mygrid.attachEvent("onRowDblClicked", function(rId,cInd){
//do something
});


//====Sub grid load on created====//
mygrid.attachEvent("onSubGridCreated",function(subgrid, id){
//collapse all other subgrid
//notice hiding column #2
subgrid.setHeader("#master_checkbox,,Industry,Advertiser");
//notice chekbox @ column #1
subgrid.setColTypes("ch,ro,ro");
subgrid.setInitWidths("40,0,100,100");
subgrid.init();
subgrid.load("http://example.com/path_tosubgrid_json" + id,function() {
//call to reload subgrid height
subgrid.setSizes();
subgrid.callEvent("onGridReconstructed", []);
}, "json");
return false; // block default behavior
});


//updates dec 19: this can be done by calling mygrid.getCheckedRows(0);
//overriding method to get all checkbox ticked in main grid
mygrid.getSelectedRows = function() {
var aResult = [];
this.forEachRow(function(id){
var cell=this.cells(id,0);
if (cell.isCheckbox() && cell.getValue()==1) {
aResult[aResult.length] = id;
}
});
return aResult;
}

//===to reload data of a page
mygrid.rowsBuffer = dhtmlxArray();
mygrid.changePage(this.currentPage);


//===to only allow expand of 1 subgrid
//if expand row
mygrid.attachEvent("onSubRowOpen", function(expandId, status) {
if (status) {
mygrid.forEachRow(function(id){
if (id!=expandId) {
mygrid.cells(id, 0).close();
}
});
}
});

//=== for custom sorting===//
mygrid.attachEvent("onBeforeSorting",function(ind,type,direction){
var iLimit = 10; //limit records / page
var sSortDir = direction;
var sSortBy = aRawCols[ind];
var iOffset = (mygrid.currentPage-1)*iLimit;
var sNewURL = "http://example.com/path_to_json_loader?&count=" + iLimit + "&posStart=" + iOffset + "&dir="+sSortDir+"&sort="+sSortBy;
mygrid.load(sNewURL, "json");
this.setSortImgState(true,ind,direction);//set a correct sorting image
return false;
});

Please note that, dhtmlxgrid loading behaviour is rather, "special case".
Most Grid will load data, and assume the data return is starting on index 0 from offset pass in,
but somehow, it skipped the records up to offset before collecting data for the new page.
even with posStart=2, your json data has to return postData of emty array of 2 before appending actual row of data.
Example:
[
[],[], {id:3, name: "xxx"},...
]


Grid with sub grid

Working with ExtJS grid was kind of cool. But if you need to have another grid within the grid, the Grid selection, checkbox and expand column become hair wire on ExtJS.
I've tried several ways, but some how given up on it. Hope fully ExtJS 3.4 or higher could see the light on this feature...

After searching around, DHTMLXGrid did serve the purpose pretty well.
But the documentation is a bit confusing, need a bit of digging on the doc and the code in order to configure things up to what you might need.

Reference:
+ http://docs.dhtmlx.com/doku.php?id=dhtmlxgrid:api_toc_alpha

Wednesday, December 15, 2010

MODx 2.0.5 form customization for profile

The new form customisation for MODx 2.0.5-pl seems to only cater for resource create/update.

If you need a form customization for user profile, you may do it by adding a plugin on initialized manager with event "OnManagerPageInit":
-----------
define("IS_PROFILE_MODULE", $_GET["a"] == 71? true: false);
if (IS_PROFILE_MODULE) {
$modx->regClientStartupScript("url to your file.js",true);
}
-----------

URL to your file.js:
-----------
Ext.onReady(function() {
doHideProfileTabs();
});

function doHideProfileTabs() {
if (!Ext.getCmp("modx-panel-profile-tabs")) {
setTimeout("doHideProfileTabs()", 1000);
return;
}

MODx.hideTab("modx-panel-profile-tabs","modx-panel-profile-update");
MODx.hideTab("modx-panel-profile-tabs","modx-profile-recent-docs");
//MODx.hideTab("modx-panel-profile-tabs","ext-comp-1020"); //change password
Ext.getCmp("modx-panel-profile-tabs").setActiveTab(2);
}
-----------
Please take note: The hidden tab is still in the tabs container, therefore setactivetab have to reflect the index of the original position.

MODx Updating to new version

files needed to copy:
core/* , except core/cache, core/config
manager/*
connectors/*
setup/*
index.php

Remember to avoid copying:
core/cache
core/config

Friday, December 10, 2010

Modx 2 getResources enhanced tvFilters

I've done some modification to getResources to accomodate more condition operators such as :
!=
EXISTS
NOTEXISTS
| : for OR operator

And changed that the condition will only effect after the main condition is meet, etc. published='1' and deleted='0'

Examples:
tvFilters=`mytv!=%xxx%|mytv==NOTEXISTS`
#setting condition to TV mytv does not contain tag "xxx" or no mytv variable exists

getResources2.php

Hope it helps you guys :)
Cheers~

Tuesday, December 7, 2010

Modx 2 file manager + upload image on 2.0.4-pl2 or lower

File manager works well when you plan to use only "web" context for your manager users.
But when it comes to using other context, you will realise that there are some permission issue related to context.

To fix it temporary, until modx 2.0.5 is release, here is a fix.
path:
model/modx/processors/browser:

*/directory/getlist.php , */directory/getfiles.php , */file/upload.php , */file/remove.php:
Adding this somewhere at the top:
$context = !isset($scriptProperties['ctx']) ? "web": $scriptProperties['ctx'];

Change from:
$root = $modx->fileHandler->getBasePath();
To:
$root = $modx->fileHandler->getBasePath(false, $context);
---------------
Change from:
$fileManagerUrl = $modx->fileHandler->getBaseUrl();
To:
$fileManagerUrl = $modx->fileHandler->getBaseUrl($context);
----------------

Wednesday, December 1, 2010

MODx 2 permissions for uploading image in resource editor

Minimum Requirement:
Context access:
Context: "mgr", min role: "member",
access policies: "file_upload, file_manager"

Context: "web/yourcontext", min role: "member",
access policies: "file_upload,file_list,file_update,file_remove"

Optional access policy:
directory_list
directory_update
directory_create
directory_remove
directory_chmod

javascript string replace all?

Javascript String "replace" function only replace 1 instance of the string.
If you need a replaceAll function, here is a handy function:

function replaceAll(source, find, replace) {
if (source == null) { return source; }
var aString = source.split(find);
return aString.join(replace);
}

Sunday, November 28, 2010

MODx IE login problem

It seems that Internet explorer has some problem with login on "some" machine.
The culprit result to cookie issue, where by resetting cookie on the client browser seems to fix the issue.
But asking user to clear their cookie is a near to impossible things todo.

A nice tweak would be resetting the cookie via javascript on the login form itself.
Example:
document.cookie = "PHPSESSID=; __utma=; __utmc=; __utmz=; __utmb=; path=/; domain=.example.com; expires=Thu, 01-Jan-1970 00:00:01 GMT;";

Saturday, November 27, 2010

ExtJS datefield

Here is an example of a date field in extjs:

Ext.onReady(function(){
fieldname = new Ext.form.DateField({
dateFormat: 'd-m-Y', //display format
value: "2010-01-01",
selectOnFocus:true,
minValue: '2010-01-01', //optional start limit
maxValue:'2010-12-31', // optional end limit
applyTo: 'frmMyDate',
});
});

MODx 2 Datetime field

Here is an example of a datetime field in modx 2 [revo] manager:

Ext.onReady(function(){
fieldname = new Ext.ux.form.DateTime({
dateFormat: 'd-m-Y', //display format
hiddenFormat:'Y-m-d H:i:s', //actual data format
value: "2010-01-01 01:00:00",
selectOnFocus:true,
applyTo: 'frmMyDate',
});

});

If you use xtype, its named "xdatetime"

Thursday, November 25, 2010

ExtJS Get form input field handy tips

Getting the field by name:
var inputfield = target.getForm().findField("fieldname");

Hiding a field:
target.getForm().hideField(inputfield);

Showing a field
target.getForm().showField(inputfield);

Wednesday, November 24, 2010

ExtJS dateformat

To render the date format of a date column within a grid, we will need to setup the fields for the grid correctly in the config.

field: [{name: 'fieldname'}, {name:'createdon', type='date', dateFormat=''}]
dateFormat options:
  • m/d/Y or Y-m-d H:i:s
  • timestamp: Unixtime
  • time: (TODO)
To render to your own date format on the grid:
columns: [ {id:'createdon',header: _("createdon"), width: 100, sortable: true, renderer: Ext.util.Format.dateRenderer('d M Y'), dataIndex: 'createdon'}
]

Reference on more dateformat:

ExtJS Datagrid column sortable

I've just realized that if you dont set id for the columns of the datagrid,
the sorting option for the column will be disabled by default, even with sortable set to true.

,columns: [
{id:'id', header: _("id"), width: 80, sortable: true, dataIndex: 'id'},
{id:'pagetitle', header: _("pagetitle"), width: 400, sortable: true, dataIndex: 'pagetitle'}
]

Hope this help you save hours of debugging :)
cheers~

MODx 2 Manager Alert and Prompts examples

Success example:
MODx.msg.alert(_('success'), ("Saved"));

Error example:
MODx.msg.alert(_('error'), ("Some problem occur!"));

Prompt Example:
Note: code highlighted in italic are just sample code, will not work without full code.
---------------------------------------
MODx.msg.confirm({
title: "Delete Article?"
,text: "Confirm delete this article?"
,url: MODx.config.connectors_url + "resource/index.php"
,params: {
action: "delete",
id: selectedIndex
}
,listeners: {
'success': {
fn: function (A) {
if(A.success) {
MODx.msg.alert(_('success'), ("Removed"));
mygrid.refresh();
} else {
MODx.msg.alert(_('error'),A.message);
}
},
scope: this
}
}
});
---------------------------------------

_('...') is a function to output lexicon text (translation text)


Monday, November 22, 2010

ExtJS

Extjs was extended from originally the open source version of YUI.
I find that, due to lack of easy google search on documentation / tutorial on extjs (or maybe because im not good at looking for extjs keywords), i find it quite difficult to pick it up.

I've found an example for jQuery lover:
jQuery examples:
jQuery("#my_field_id") or $("#my_field_id")

ExtJS:
Ext.get("my_field_id"); // return single element with id
Ext.select("#my_field_id div span a");// return array, alike jQuery selector

I've found a good basic tutorial here:

Sunday, November 21, 2010

useful modx 2 variables

PHP Constants:
MODX_BASE_PATH - dir path to index.php (root)

MODX_ASSETS_PATH - dir path to assets
MODX_ASSETS_URL - url to assets

MODX_CONNECTORS_PATH - dir path to connector
MODX_CONNECTORS_URL - url path to connector

MODX_PROCESSORS_PATH - dir path to processor: example: core/model/modx/processors/

MODX_CORE_PATH - dir path to core

MODX_SITE_URL - domainurl
MODX_HTTP_HOST - hostname / domain

MODX_URL_SCHEME - http:// / https://

MODX_MANAGER_PATH - dir path to manager
MODX_MANAGER_URL - url to manager

$modx->context->get("key"); // get context name
$modx->context->getConfig("upload_maxsize"); // example of getting upload max size


JAVASCRIPT Constants:
MODx.config.http_host, example www.example.com
MODx.config.http_host_remote, example http://www.example.com
MODx.config.site_url , example: http://www.example.com/
MODx.config.assets_path, example "/assets/"
MODx.config.connectors_url, example "/connectors/"
MODx.config.manager_url, example "/manager/"
MODx.config.template_url, example "/manager/templates/default/"


Wednesday, November 17, 2010

New Modx 2.0.5 Profile Set

Damn cool :D
[Link here: http://bit.ly/aXTvxB]

Modx 2 getUser

Instead of calling $modx->getUser to get currently logged in user,
use getAuthenticatedUser.

Example:
$oUser = $modx->getAuthenticatedUser($modx->context->get("key"));
echo $oUser->username;
echo $oUser->getOne("Profile")->fullname;

If you use "getUser", it might return the first user in the database if the person is not logged in, which might not be what you are looking for.

hope it helps :)
Cheers~

Tuesday, November 16, 2010

Ping server from many different country

http://www.just-ping.com

DNS server temporary down

Update: DNS service is up and running...

Update 6.10pm:
Some connection seems okay, but still unstable on many other network...

Hi,
right now, we are having some packet lost on the network to our dns server.
Please be patient and wait for the network team to work on the issue.

best regards,
James

Sunday, November 14, 2010

Modx 2 How does getService works?

modx->getService($name, $class= '', $path= '', $params= array ())

name = name to be assigned to modx->$name and modx->services[$name]
class = classname (resolving to "classname".class.php)
path = path to look for class
params = array of value pass to class.

Example:

getService("login", "Login", corepath."/components/login", ...);
Resolve to:
core/components/login/Login.class.php
initiate class __construct(modxobject, $params);



Wednesday, November 10, 2010

Modx 2 [revo] with PHPUnit

Test driven development is quite important in developing useful code.
As we test each single functionality, we might find some bug which may exists in the framework we are using.

I'm going to show how phpunit could be use with modx for testing.
First, install phpunit through pear module, which the reference can be found here:

http://www.phpunit.de/manual/current/en/installation.html

Once installed, you can now starting coding by putting up these codes above your test class:
----------------------
define('MODX_API_MODE', true);
//define("MODX_HTTP_HOST", ""); //this is optional, set it to the url of your context
global $modx;
require(dirname(dirname(__FILE__)) . "/index.php"); //path to modx root/index.php
$modx->getRequest();
----------------------

Now, we are ready to create the test unit.
Example:
--------------------------
class TestDB extends PHPUnit_Framework_TestCase {

public function testSuite() {
$this->doTestDatabase();
}

public function doTestDatabase() {
global $modx;
$user = $modx->getObject("modUser", 1);
$this->assertEquals(1, $user->get("id"));
}
}
--------------------------
Running the test:
phpunit TestDB.php

Hope it helps ya :)
Cheers~

XPDO save with addOne

updates: dec 16: My mistake, ive used addOne on a one to many relation, therefore, should use addMany instead of addOne..

-------------

im not sure if this is a bug or the way it should be working.

It seems that if we addOne() to a new object, and save the parent object, the child object and parent object get saved.

But if we edit an existing parent object, and use addOne, the child object did not get saved when i call parentobject->save()...
Please note that the parent object is not modified in my case.


updates: dec 13: relying on addOne to save record is not recommended. Best to set the foreign key value manually.

Hope this help ya :)
Cheers~

Sunday, November 7, 2010

Modx 2 creating child on a protected resource

It seems that, there is some behavior in modx 2 which works kind of odd.
With a resource assigned to resource group, user will need to have 2 different policies under user group to be able to create child under the protected resource.

+ Resource group: "your resource group name", min role: "Member", Access policies: "Administrator", Context: "mgr"
+ Resource group: "your resource group name", min role: "Member", Access policies: "Resource<", Context: "mgr"

Hope it helps for ya ;)
Cheers~

Wednesday, November 3, 2010

Modx 2 Limiting manager screen user to certain context

How do i protect my resources and allow only certain context editable by manager users A?

Filter out context #1 , and only allow context #2 to be edited by user A
First:
Grant permission to the other context (not to be shown to user A) to administrator user group
- Edit Adminisrator user group
- Context Access
- Add context:
+ Context: #1
+ Minimum role: 0
+ Access policy: Administrator

2nd:
Create a role for user A, with ID less than 9999, but above authority level 0, example:
- Role: "Editor"
- authority level: 1000

3rd:
Create the user group for user A:
Example:
- "Editors"

4th: Add user A to user group "Editors"
Security: Access Control: User groups, right click, edit user group "Editor"
Users: Add user to group
Example:
- User: User A
- Role: "Editor"

5th: Goto: Security: Access Control: Access Policies
Right click resource policy, duplicate.
Edit "Duplicate of Resource" policy, permission tab.
Add "resource_tree", and rename policy name to "ResourceTree" and save.
*Note: this will allow showing of resource tree when user is editing context #2


6th: Goto: Security: Access Control: Access Policies, Add policy:
Example:
- Policy: "Editor admin", save. (to be used in manager context for user A)
Go to permission tab.
Add the following permissions:
+ change_profile
+ class_map
+ countries
+ edit_document
+ frames
+ help
+ home
+ load
+ logout
+ resource_tree
+ save_document
+ view
+ view_document
+ new_document
Save.

7th: Goto: Security: Access Control
Right click on "Editors", update user group, Goto context access tab.
Add 3 type of context:
1. Context: mgr, min role: "Editor" (1000), Access policy: "Editor admin"
2. Context: "Context #2", min role: "Editor" (1000), Access policy: "ResourceTree"
3. Context: "Context #2", min role: "member" (9999), Access policy: "Load, list and view"


Now you shall have User A be able to access and edit document in context #2, but context #1 will be hidden from User A.
And anyone logged in as Member of user A will only be able to load,list and view resources created by User A.

And please remember to flush permissions, and relogin the user to test.

How about limiting resources in Context?

This can be done by using resource group.
Example:
Those resources that you do not wish to allow user to edit resource group A
While those resource you wish to allow user to edit as resource group B

1st, setup the resource group A and resource group B.
Then assign resource to resource group accordingly.

2nd, update usergroup "adminisrator",
And goto "Resource group access", and add 2 access:
- Resource group A; min-role: super user; policy: administrator; Context: mgr
- Resource group B; min-role: super user; policy: administrator; Context: mgr
*Note: the above set both group A and B be accessible by administrator users

3rd, update usergroup "Editor",
goto "Context access" tab:
Add 2 access:
- context: mgr; min-role: "editor"; policy: "Editor admin" (as setup above)
- context: "web/mycontext/context #2", min-role: "editor", policy: "load, list, view" (this will allow resource_tree to list resources within the context"

Then, goto "resource group access" tab:
Add access:
- resource group: "resource group B", min-role: "editor"; policy: "administrator", context: mgr (this will allow view/editing/new resource)

Done :)
Hope it helps for you...
Cheers~

Modx 2 Save bar missing

If you ever encounter save bar went missing when you try to edit the resources,
you will realise it only happen to weblink / symlink type of resources.

I realise this is due to the problem of tiny mce plugin, which in my case, might be obsolete due to some upgrading issue.

What you may do is keep uninstall and force-removal on tinymce plugin until you no longer see any version of it.
Then, in package manager, re-add the package again.
Hope it works for ya :)

Cheers~

Tuesday, November 2, 2010

Modx 2 protected content

Modx default permission setting will redirect user to an unauthorised page when the user did not have permission to view the page.

What if you want to show a teaser of the document?
Here is how:
Create the resource, and do not set the page permission.
Instead, set it to a template which output the resource content by a snippet.

Example template:
[[!out.content? &content=`[[*content]]` &guest=`[[*introtext:empty=`[[*content:ellipsis=`600`:striptags]]`]]` &role=`myrole`]]
-------------
This example above will pass in summary of 600 charactors text for guest while output the full content on Logged in with role specified

-------------
Example snippet:
-------------
$aGroups = $modx->getUserDocGroups(true);
if ($aGroups != null) {
foreach($aGroups as $sGroup) {
if (strtolower(trim($sGroup)) == strtolower(trim($role)) ) {
return $content;
}
}
}

return $guest;

Hope it helps ;)
Cheers~

Sunday, October 31, 2010

Facebook fb-req-form howto?

Ive found many misleading website which teach some of the old facebook way of doing things.
There fore, the new example in the facebook php library is the latest concept, where the parameter are passed in via array of keys.

First, setup the namespace into html tag:
<html xmlns:fb="http://www.facebook.com/2008/fbml">


Init the page via code in body, somewhere before /body


<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '[your_app_id',
session : [php code], // please refer to php example which comes with facebook official library
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true // parse XFBML
});

// whenever the user logs in, we refresh the page
FB.Event.subscribe('auth.login', function() {
window.location.reload();
});
};

(function() {
var e = document.createElement('script');
e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
e.async = true;
document.getElementById('fb-root').appendChild(e);
}());

You do not need to include the FeatureLoader.js.php. I belief this include is meant for old Facebook api.
Hope this works for you :)
p/s: facebook always change their api, therefore, if it doesnt work, try to google around :)
cheers~

Tuesday, October 26, 2010

Modx 2 syntax

[ [...] ] - Calling snippet

[ [$...] ] - Calling chunk

[ [*...] ] - output / return modx setting or page variable, example [ [*pagetitle] ], [ [*content ] ]

[ [+...] ] - output / return variable from snippet call into chunk

[ [!... ] ] - Calling snippet without cache

[ [%...] ] - output Lexicon value (language)

Monday, October 25, 2010

Quip missing chunks

Anyone having the same problem as me, here are the missing chunks in quip 1.2.0-rc2
Updates Oct27: This chunks are not needed as its the default chunk in files, packaged with quip.

To install:
Upload the zip file to core/packages/
then go to package manager > add new package
select "Search Locally for Packages"
click "yes" when prompted.

When done, you will notice the Quipchunks component in the list

Sunday, October 24, 2010

extJS on Chrome vs Firefox

The new modx 2 seems to be using extJS as their manager panel javascript ui.
Its a heavy framework, but proof to be quite handy.
But running on firefox is quite a problem.
As the longer you are using it, you will realise the memory slowly hike up, and you might be ending up having the entire browser slower and slower.
While in chrome, it works pretty smooth the entire time. So far havent notice any significant memory hiking. And performance changes have never been felt.
And comparing both chrome and firefox performance, chrome works faster in rendering the ui.

Version compared:
Firefox 3.6
Chrome 6.0

Saturday, October 23, 2010

Modx 2 Category access

By default, everyone have access to any category.
Any resources fall within those category will be editable and save by anyone who have the policy to save/edit_template/snippet/chunk.

To restrict permission, you will need to add the category into the user group with:
category: [youcategory]
min role: [member/super user]
access policy: administrator
context: mgr

Once save, you will realise, nobody will be even see the category anymore.
Solution, Running: Security > Flush permissions.
And now, only those who meet min role within mgr may see the category and the element/resources inside.

Updates Oct25: There is a bug in modx2 which doesnt allow new element to be created under the category we have provided administrator policy as set above. The work around is to create a new element without any category, and then edit the element and set category to it. It will work properly if we set the element to it.
http://bugs.modx.com/issues/2610


Okay, now if you want other user to see only those resources/element inside the category but do not want to let them edit:
Add this in the user group category:
category: [youcategory]
min role: [member/super user]
access policy: Load, List and View
context: mgr
Running: Security > Flush permissions.

If the above doesnt work, try logout and login again :)
Hope it helps :)

Friday, October 22, 2010

PHP mkdir permission

PHP Bummer when comes to mkdir permission.

mkdir($yourfolder, 0777); //somehow set the folder permission to 0755
chmod($yourfolder, 0777); //this works after mkdir :)

Modx 2 Getting Action ID

I've found a way which is easier to get the action id of the components.
Simply assigning a variable to $_GET["a"] in the controller file within core/yourcomponentname/controllername shall do.

As all action which requires the component to work will require the action id to pass in, therefore, the initial file to run will be the controller file.

Wednesday, October 20, 2010

Modx 2 get document by alias

$columns = array_keys($modx->getFields('modResource'));
$criteria = $modx->newQuery('modResource');
$criteria->setClassAlias('sc');
$criteria->select($columns);
$criteria->andCondition("sc.published=1 and sc.deleted=0 and sc.alias='" . mysql_escape_string($alias) . "'");
$criteria->limit(1);
//$criteria->prepare();
//return $criteria->toSQL();
$objCollection = $modx->getCollection('modResource', $criteria);
$collection = array();
foreach ($objCollection as $obj) {
array_push($collection, $obj->get($columns));
}
if (count($collection) <>
return $collection[0]["content"];

Modx 2 snippet bummer

If you code [[*value]] in snippet php coding, you will realise the value gets populated when you return it.
But, if you are using the value in [[*value]] to check some condition, then you are out of luck.
The real value isnt populated during the script process.
Its only populated after its been processed.

Alternative way is to pass in value to snippet:
[[!mysnippet? &myvalue=`[[*value]]`]


Modx 2 processed tv to display

Previously ive talked about using "input options value" to output list of keys and values for dropdown list or options.
example: "@select listlabel, listvalue from modx_listvalues where listkey='postcategory' order by weight asc, listlabel asc, id asc"

But to output the value, its not the same.

if you installed getResourceField,
adding processTV=`1` and useTV=`1` field=`your_tvfieldname`

processedTV is obtained from the value in the TV variable.
So instead of the value directly "test", you have to put in your binding syntax as value.
Example: "@select listlabel from modx_listvalues where listkey='postcategory' and listvalue='test';

If you dont like the way of putting syntax into the value box, you may try to use snippet to pass in the value to get the value to display. I think this is the better way.

Thursday, October 14, 2010

Modx 2 Login Snippet

The login snippet installable from package management have several flaw in certain usage.
We have a client who has login form @ the very top.

When user login into the site, the user will be redirected to to site_url (start) page if the last page is his/her first visit or landing page.
@solution: Edit Login snippet, and comment off line #155
//$modx->sendRedirect($modx->getOption('site_url'));

2nd problem, since we have disabled redirect, user will be known as "not yet authenticated" in the current page, unless he/she click to another page.
@solution: Edit Login snippet, and append this line #148
$authenticated = true;

Modx 2 query for template variables

This example shows how to query for template variable from a article:

SELECT cv.*, v.`name` FROM `modx_site_tmplvar_contentvalues` cv left join `modx_site_tmplvars` v on cv.tmplvarid=v.id
WHERE `contentid` = #

Wednesday, October 13, 2010

Modx 2 showing error page instead of unauthorize page

In modx 1, its a direct method to setup article / post permission.
1st, setting up resource group. Then setup user group, and add in resource group to user group.
Next, assign user to user group.
Finally, when someone not logged in, the user will be shown a unauthorized page.

But in modx2, there are extra steps to setup this.
Notice that modx2 now have "minimum role", "access policy", "context".
1st. setting up resource group. Then setup user group. Right click on user group, and click "Update user group".
Click on "Resource Group Access" tab, "add resource group"
Select the resource group you created.
Minimum Role: Member (9999)
Access policy: "Load, list and view"
Context: Web

This allow user of type member to load the document, or list it out in rss and also view the document.

Now that you have protected this page, you will notice the unauthorised user will see a error page when trying to view this page. By right, the guest should see the unauthorized page instead...

Okay, to fix this, Go to "Access Control",
Right click on "(Anonymous)", and click "Update user group".
Click on "Resource Group Access" tab, "add resource group".
Select the resource group you created.
Minimum Role: Member (9999)
Access policy: "Load"
Context: Web

This will allow guest to load the document, but will not allow guest to list or view the document. This is how modx2 policy works.

Hope it helps ;)

Tuesday, October 12, 2010

Modx 2 Bummer in XPDO Log

It seems that modx developer have did a mistake by setting debug true/false to xpdo debug with php ERROR LEVEL constant

having : int(30711) for E_ALL & ~E_NOTICE in core/model/modx/modx.class.php line 453:
parent :: setDebug(intval($debug)); // isn't going to make any sense to xpdo debug

to fix it, ive manually
set it to true.

output path:
core/cache/logs/error.log

Friday, October 8, 2010

modx2 unable to right click Action

If you ever encounter problem right clicking on action box to create action for a namespace,
you may still do it manually by inserting a record in table "modx_actions"
  • create namespace in manager
  • insert record into modx_actions
    namespace=[namespace_name], parent: 0, controller: index, haslayout: 1
  • clear cache in manager
Tada, it will be there.

Modx2 accidental removed core Action

If you ever accidently removed the core Action ,
you may restore the file @
core/cache/mgr/actions.cache.php

Modx2 deployment

To transfer testing site to live site:

  • restore database to server
  • upload files to serveredit core/config/config.inc.php (many path setting)
  • server edit config.inc.php
  • server edit manager/config.inc.php
  • server edit connectors/config.inc.php
  • edit table modx_workspaces and set path field to core ,
    example: '/var/www/modx/core/'
  • for provisioner package: setting modx_system_settings.value of key "cookiefile" to temporary folder (if its not /tmp)
  • set chmod -R 777 to core
  • set chmod -R 777 to assets (create if not exists)
  • login manager, and clear cache (ignore the error of unable to locate action file)
If you have problem with packages, and accidently deleted the zip file, you may either download the package file manually, or delete the record in table "modx_transport_packages"

Newer version (2.1.3) doesnt need to edit manager, connectors config.inc.php.

First thing after done all above, login to manager, and clear cache.
But if you encounter unable to locate file ...manager/controllers/default/welcome.php
Try rerun setup

Useful links:

Thursday, October 7, 2010

Modx 2 how to output TV (template variable)

[[+tv.namehere]]

Make sure to have set:
&includeTVs=`1` in getResources snippet call

Example:
[[+tv.post_eventdate:strtotime:date=`%b %d, %Y`]]

Wednesday, September 29, 2010

Modx2 resourcegroup (documentgroup) with usergroup

In Modx1,
modx_web_groups.webuser=#userid
modx_webgroup_access.webgroup=modx_web_group.webgroup
modx_webgroup_access.documentgroup

In Modx2,
modx_member_groups.member=#userid
modx_access_resource_groups.principal_class='modUserGroup', principal=modx_member_groups.user_group
modx_access_resource_groups.target


principal: membergroup_names.id
target: modx_documentgroup_names.id / "web" / "manager"

Server CheckList

List of checklist on setup of any LAMP server:
  • timezone /etc/localtime
  • php timezone /etc/php5/apache2/php.ini, /etc/php5/cli/php.ini
  • checking the date after setting timezone
  • install/configure for rewrite for apache2, with Allowoveride all
  • install/configure GD2, xml, mbstring, pdo, pdo_mysql for php
  • configure PHP:
  • register_global=off, memory_limit=40mb+, magic_quote_qpc=off ,
  • file_uploads=1, upload_max_filesize=10Mb+, max_input_time=300+,
  • max_execution_time=60, post_max_size=15Mb+
  • display_errors=1, error_reporting=E_ALL, log_errors_max_len=10240

Monday, September 27, 2010

modx2 event

  • OnInitCulture
  • OnHandleRequest
  • OnWebPageInit
  • OnPageNotFound (if not found)
  • OnLoadWebPageCache
  • OnLoadWebDocument
  • OnParseDocument
  • OnWebPagePrerender
  • OnBeforeSaveWebPageCache
  • OnWebPageComplete

Sunday, September 26, 2010

modx2 getResources vs ditto

Modx2 uses getResources + getPage to replace ditto snippet from Modx1.
Syntax wise, there are similarity but slightly different in usage.

ditto: display=`20`,
modx2: getResources &limit=`20`

ditto: &paginate=`1`,
modx2: getPage &pageVarKey=`page` &element=`getResources` &elementClass=`modSnippet`

ditto: &extenders=`summary,dateFilter`&dateFormat=`%b %d, %Y` &sortBy=`pub_date` &sortDir=`DESC` &dateSource=`pub_date`
modx2: &sortby=`publishedon` &sortdir=`desc`&sortby=`publishedon` &sortdir=`desc`

ditto chunk: [+date+], [+summary+]
modx2: [[+publishedon:strtotime:date=`%b %d, %Y`]], [[+introtext:empty=`[[+content:ellipsis=`250`:striptags]]`]]

And some additional parameter not set default by modx2 getResources:
&showHidden=`1` &includeContent=`1`

And display page number:
[[!+page.nav:notempty=`
    [[!+page.nav]]
    `]]

    For reference:
    http://rtfm.modx.com/display/ADDON/getResources


    Example:
    [[!getPage? &limit=`20` &parents=`4733` &showHidden=`1`&sortby=`publishedon` &sortdir=`desc` &pageVarKey=`page`&element=`getResources` &elementClass=`modSnippet` &tpl=`ditto_investoraudit_summary2` &sortby=`publishedon` &sortdir=`desc` &includeContent=`1`]]

    [[!+page.nav:notempty=`
    <div class="paging">
    <ul class="pageList">
    [[!+page.nav]]
    </ul>
    </div>
    `]]

    Wednesday, August 25, 2010

    PHP Big Bummer!

    When a string is set to empty String "":
    --------------
    $myvar = "";
    echo is_null($myvar) ? "is null" : "not null";
    //return not null
    echo $myvar==null ? "is null" : "not null";
    //return IS NULL
    //whattttt????
    =========
    When a array is set to empty String "":
    $myvar = array("myname" => "");
    echo is_null($myvar["myname"]) ? "is null" : "not null";
    //return not null
    echo isset($myvar["myname"]) ? "is set" : "not Set";
    //return NOT SET!
    //whatttttt????
    echo $myvar["myname"] == null ? "is null" : "not null";
    //return IS NULL!
    //what??????????

    Thursday, August 19, 2010

    Modx 1.04 Event Sequence

    List of events in sequence
    - OnBeforeManagerPageInit (DocumentParser::getSettings), if is manager
    - OnWebPageInit (DocumentParser::executeParser)
    - OnLogPageHit (DocumentParser::executeParser), if track_visitors is on
    - OnLoadWebPageCache (DocumentParser::prepareResponse)
    - OnLoadWebDocument (DocumentParser::prepareResponse)
    - OnParseDocument (DocumentParser::parseDocumentSource)
    - OnWebPagePrerender (DocumentParser::outputContent)

    Wednesday, August 18, 2010

    Double action (GET & POST) posted! Modx Manager

    When you encounter this type of error, its due to the specification of
    query-string and post form with "a" parameter

    Monday, August 2, 2010

    Modx ChangePwd Chunk sample

    <div id="divChangePassword" style="position: relative; display: none;">
    <form method="POST" name="changepwdfrm" action="[+action+]">
    <input type="hidden" name="cmdwebchngpwd" value="1" />
    <input type="hidden" name="passwordgenmethod" value="spec" />
    <input type="hidden" name="passwordnotifymethod" value="e" />
    <div><label for="oldpassword">Existing Password</label><input type="password" name="oldpassword" /></div>
    <div><label for="specifiedpassword">New Password</label><input type="password" name="specifiedpassword" /></div>
    <div><label for="confirmpassword">Retype Password</label><input type="password" name="confirmpassword" /></div>
    <input type="submit" value="Save" />
    </form>
    </div>

    Some useful way to get ip and computer name via php

    To get the user’s IP use the following script:
    phpGetHostByName($REMOTE_ADDR);

    To get the user’s computer name use the following script:

    phpgethostbyaddr($_SERVER['REMOTE_ADDR']);

    Sources:

    Friday, July 9, 2010

    Disabling captcha when captcha is not being display

    update `modx_system_settings` set setting_value='0' where setting_name='use_captcha';

    Tuesday, June 29, 2010

    Alfresco installation: installing pdf2swf

    For redhat / rpm based distribution:
    yum install zlib-devel libjpeg-devel giflib-devel freetype-devel gcc gcc-c++

    For debian / ubuntu / apt based distribution:
    sudo apt-get install zlib1g-dev libjpeg-dev libgif-dev libfreetype6-dev

    ./configure

    make

    sudo make install

    Monday, June 7, 2010

    URL Rewrite for alias with parameter

    When we need to redirect certain alias to another url,
    we can modify htaccess file to handle redirection with the code below:

    Example: redirect blabla to /articles/index
    RewriteRule ^blabla(/)? /articles/index$1 [R,L]
    The above work if our url goes:
    http://www.example.com/blabla or http://www.example.com/blabla/

    If we may have sub folders within bla bla or need to pass in additional parameter (as common in zend framework):
    RewriteRule ^blabla(/)?(.+)? /articles/index$1$2 [R,L]
    The above work if our url goes:
    http://www.example.com/blabla/page/2/xyz/aaa
    to
    http://www.example.com/articles/index/page/2/xyz/aaa


    Thursday, May 13, 2010

    Session fixation Attack

    This kind of attack actually set your session to certain id which the server recognise and store the session by that id.

    For example:
    www.example.com/?PHPSESSID=AEXAMPLEOFHARDCODESESSIONID

    So when user click on the link, user will find that he/she need to login.
    When user logged in, the session will be active on "AEXAMPLEOFHARDCODESESSIONID".

    This way, attacker can access the same url from another machine and have access to the logged in session.

    Solution
    Everytime the user login, reset the session id to a new id. This way, a new session id is set on every login attempt. Remote attacker will not be able to guess the id being used as its generated randomly by server.

    Reference:

    Monday, May 10, 2010

    PHP on Apache Vs IIS

    There are many variables which only available on apache, but not on IIS.
    Here are a good list of items available on both apache and IIS.

    Sunday, May 9, 2010

    Multi-lingual codes with countries

    This are reference to some who will find it useful to develop multi-lingual website.

    List of Language Codes:

    Here are some flag icons you may use:

    Wednesday, May 5, 2010

    TimeZone list for php and iCal

    Sample iCAL:
    BEGIN:VCALENDAR CALSCALE:GREGORIAN X-WR-TIMEZONE;VALUE=TEXT:US/Pacific METHOD:PUBLISH PRODID:-//Apple Computer\, Inc//iCal 1.0//EN X-WR-CALNAME;VALUE=TEXT:Example VERSION:2.0 BEGIN:VEVENT SEQUENCE:5 DTSTART;TZID=US/Pacific:20021028T140000 DTSTAMP:20021028T011706Z SUMMARY:Coffee with Jason UID:EC9439B1-FF65-11D6-9973-003065F99D04 DTEND;TZID=US/Pacific:20021028T150000 BEGIN:VALARM TRIGGER;VALUE=DURATION:-P1D ACTION:DISPLAY DESCRIPTION:Event reminder END:VALARM END:VEVENT END:VCALENDAR
    List of time zones code:
    http://www.php.net/manual/en/timezones.america.php


    Wednesday, April 28, 2010

    Model in MVC

    There are many peoples misunderstood the concept of model.
    Model are suppose to represents the entire system for which that data is useful.
    A good MVC system shall have small controller compared to a Fat Model.

    More useful reading here:

    How to educate clients on web development

    Todo: More description to come...

    Slide11 in How To Successfully Educate Your Clients On Web Development

    http://www.smashingmagazine.com/2010/04/23/educating-your-client-on-web-development-successfully/

    Wednesday, April 21, 2010

    Koki Konishi Principle for Toyota Institute

    1. Maintain historical perspective:
      All planning shall take consideration on what has been done in the past.

    2. Assess with global perspective:
      Look at whats happening in america and australia? and other big country?
      How do they corelate to work together?

    3. Rely on ethics:
      We have to see the world and our action not as only a business person, but also as a human. How will it impact around us on our action.

    Thursday, April 8, 2010

    web safe fonts

    Its quite common now days to have custom type face for web design.

    These browser supports TTF (true type) / OTF (open type) format:
    • Safari / Webkit 3.1
    • Opera 10 / Opera Mobile 9.7
    • Firefox 3.5 / Thunderbird 3 / SeaMonkey 2
    • Ie9 (maybe?)
    • Chrome 4
    While IE 5/6/7/8/9 supports EOT (embeded open type) format.
    @font-face will not work on chrome / safari if font-variant is set to small-cap.
    Reference: http://webfonts.info/wiki/index.php?title=@font-face_browser_support

    As for other browser, you may set alternative font-family of type face web-safe fonts:
    Arial, Arial, Helvetica, sans-serifArial, Arial, Helvetica, sans-serif
    Arial Black, Arial Black, Gadget, sans-serifArial Black, Arial Black, Gadget, sans-serif
    Comic Sans MS, Comic Sans MS5, cursiveComic Sans MS, Comic Sans MS5, cursive
    Courier New, Courier New, Courier6, monospaceCourier New, Courier New, Courier6, monospace
    Georgia1, Georgia, serifGeorgia1, Georgia, serif
    Impact, Impact5, Charcoal6, sans-serifImpact, Impact5, Charcoal6, sans-serif
    Lucida Console, Monaco5, monospaceLucida Console, Monaco5, monospace
    Lucida Sans Unicode, Lucida Grande, sans-serifLucida Sans Unicode, Lucida Grande, sans-serif
    Palatino Linotype, Book Antiqua3, Palatino6, serifPalatino Linotype, Book Antiqua3, Palatino6, serif
    Tahoma, Geneva, sans-serifTahoma, Geneva, sans-serif
    Times New Roman, Times, serifTimes New Roman, Times, serif
    Trebuchet MS1, Helvetica, sans-serifTrebuchet MS1, Helvetica, sans-serif
    Verdana, Verdana, Geneva, sans-serifVerdana, Verdana, Geneva, sans-serif
    Symbol, Symbol (Symbol2, Symbol2)Symbol, Symbol (Symbol2, Symbol2)
    Webdings, Webdings (Webdings2, Webdings2)Webdings, Webdings (Webdings2, Webdings2)
    Wingdings, Zapf Dingbats (Wingdings2, Zapf Dingbats2)Wingdings, Zapf Dingbats (Wingdings2, Zapf Dingbats2)
    MS Sans Serif4, Geneva, sans-serifMS Sans Serif4, Geneva, sans-serif
    MS Serif4, New York6, serifMS Serif4, New York6, serif