Developing extras for MODx Revolution. Part 1


On habrahabr already wrote a lot about creating components for MODx Revolution, but in my opinion a comprehensive guide to the Russian language is still there. A lot of gaps and is not entirely accurate information. But such guidance is official documentation in English. I think in this case the initiative to anything :). Although the present programmer should not be a problem with English, read in Russian I think for most enjoyable. I decided to do free translation of this fundamental, in my opinion, part of the documentation. I bring to your attention the first part. I hope you have enough patience to finish this work, and perhaps someone will help me in this. Do not worry that much text it looks complicated at first glance.

Overview

In this lesson you will learn about developing a simple add-on "Doodles" (blanks) that uses a custom database table to store objects called "Doodles", which are name and description. We will create a snippet that will print a list, formatted according to the template via the chunk, its the admin page (component) using ExtJS (part 2) and make a script for packaging in a package (package) — part 3. Also, all of this will be i18n-compatible, i.e. to have files for translation into different languages. By the way, this package can be download and learning.

Create directory structure

So, I created on your local server folder /www/doodles/. This folder I have available at http ://localhost/doodles/. Our directory structure looks like this:
Note a few things. First, we have 3 main directories: the core/, assets/ and _build/. Usually add-ons MODx Revo is divided into two directories: core/components/myextra/ and assets/components/myextra/. The directory assets/components/ contains only the web-specific files — JavaScript, CSS, etc files that are publicly available on the Internet. All PHP files will be located in the directory core/components/. The directory core/ can be moved outside of the root directory of the site (webroot) for additional security. These directories will be Packed into the transport package of our component for quick installation from the section "package Management" of the administrative part of the site. The directory _build/ is Packed in a transport package. It is needed only at the stage of creation of this package. This will be discussed in the last part of the lesson.

In the folder assets/ we will have only one PHP-file — connector.php. This file will allow us to have access to the processors in our custom control page (Custom Manager Page — CMP). More about this later.

In the directory core/components/doodles/ create a few directories:
controllers controllers for CMP;
docs — contains only the files changelog, readme and license;
elements — all of our snippets, plugins, chunks, etc.;
lexicon — all i18n language files;
model — our classes and XML schema file for our custom database tables;
processors — all of our processors for CMP.

Create snippet

Create a snippet file: /www/doodles/core/components/doodles/elements/snippets/snippet.doodles.php. Add to the file a few lines of code:

the
$doodles = $modx->getService('doodles','Doodles',$modx->getOption('doodles.core_path',null,$modx->getOption('core_path').'components/doodles/').'model/doodles/',$scriptProperties);
if (!($doodles instanceof Doodles)) return ";

Oh! What is it? This is the place where the magic happens. Let's look at each part. First, we have the invocation getService(). Divide this line into several parts for easier reading:

the
$defaultDoodlesCorePath = $modx- > getOption('core_path').'components/doodles/';
$doodlesCorePath = $modx- > getOption('doodles.core_path',null,$defaultDoodlesCorePath);
$doodles = $modx->getService('doodles','Doodles',$doodlesCorePath.'model/doodles/',$scriptProperties);

Okay, what is $modx- > getOption()? Using this method you can learn our system settings. $modx- > getService() loads the class, creates an instance of the object, if it exists, and sets it in $modx- > doodles, in this case (the first parameter passed into the method). Read more here.
Creating characters

In the control system will move to the "System" - > "system Settings". Create two new parameters with appropriate values:
doodles.core_path — {core_path}components/doodles/
doodles.assets_url — {assets_path}components/doodles/

creating a base class

Create a class file /www/doodles/core/components/doodles/model/doodles/doodles.class.php. This class we will be useful because there we can define some basic ways and methods that we will use in our component.

the
<?php
class Doodles {
public $modx;
public $config = array();
function __construct(modX &$modx,array $config = array()) {
$this- > modx =&$modx;

$basePath = $this->modx->getOption('doodles.core_path',$config,$this->modx->getOption('core_path').'components/doodles/');
$assetsUrl = $this->modx->getOption('doodles.assets_url',$config,$this->modx->getOption('assets_url').'components/doodles/');
$this->config = array_merge(array(
'basePath' => $basePath,
'corePath' = > $basePath,
'modelPath' = > $basePath.'model/',
'processorsPath' => $basePath.'processors/',
'chunksPath' => $basePath.'elements/chunks/',
'jsUrl' = > $assetsUrl.'js/',
'cssUrl' = > $assetsUrl.'css/',
'assetsUrl' = > $assetsUrl,
'connectorUrl' = > $assetsUrl.'connector.php',
),$config);
}
}


Now back to our snippet. Let's add a few properties:

the
$dood = $modx->getService('doodles','Doodles',$modx->getOption('doodles.core_path',null,$modx->getOption('core_path').'components/doodles/').'model/doodles/',$scriptProperties);
if (!($dood instanceof Doodles)) return ";

/* setup default properties */
$tpl = $modx- > getOption('tpl',$scriptProperties,'rowTpl');
$sort = $modx- > getOption('sort',$scriptProperties,'name');
$dir = $modx- > getOption('dir',$scriptProperties,'ASC');

$output = ";

return $output;


Cool. Now we want to use xPDO to query the database to capture our records... Oh. We haven't done xPDO model for them. We have to do this.

creating the model

xPDO does the abstraction of the databases in the nice query methods of the PLO. Currently, it begins to support multiple databases. In addition, it allows you to convert your strings DB in a nice, clean classes, and to make it all very short lines of code. But first we need to build this model using xPDO schema.

Create XML file /www/doodles/core/components/doodles/model/schema/doodles.mysql.schema.xml. In it put this:

the
<?xml version="1.0" encoding="UTF-8"?>
< model package="doodles" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM">
<object class="Doodle" table="doodles" extends="xPDOSimpleObject" > 
<field key="name" dbtype="varchar" precision="255" phptype="string" null="false" default=""/>
<field key="description" dbtype="text" phptype="string" null="false" default=""/>

<field key="createdon" dbtype="datetime" phptype="datetime" null="true"/>
<field key="createdby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />
<field key="editedon" dbtype="datetime" phptype="datetime" null="true"/>
<field key="editedby" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" default="0" />

< aggregate alias="CreatedBy" class="modUser" local="createdby" foreign="id" cardinality="one" owner="foreign"/>
< aggregate alias="EditedBy" class="modUser" local="editedby" foreign="id" cardinality="one" owner="foreign"/>
</object>
</model>


Look at what it all means. First line:
the
 < model package="doodles" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM">

contains the name of our pack — "doodles". She also says that our base class will be"xPDOObject", and that this scheme is made for MySQL. She also says that our storage system data MySQL default is MyISAM. Then set the properties of a database table. No need to create a field with ID, because it always creates the object and sets xPDOSimpleObject auto-increment.

the
<aggregate alias="CreatedBy" class="modUser" local="createdby" foreign="id" cardinality="one" owner="foreign"/>
< aggregate alias="EditedBy" class="modUser" local="editedby" foreign="id" cardinality="one" owner="foreign"/>


This creates a kind of connection with other objects xPDO (in this case a user object).

Script parsing schema

Now it's time to pay attention to our directory _build/. Create a file in /www/doodles/_build/build.config.php like:

the
<?php
define('MODX_BASE_PATH', '../');
define('MODX_CORE_PATH', MODX_BASE_PATH . 'core/');
define('MODX_MANAGER_PATH', MODX_BASE_PATH . 'manager/');
define('MODX_CONNECTORS_PATH', MODX_BASE_PATH . 'connectors/');
define('MODX_ASSETS_PATH', MODX_BASE_PATH . 'assets/');

define('MODX_BASE_URL','/modx/');
define('MODX_CORE_URL', MODX_BASE_URL . 'core/');
define('MODX_MANAGER_URL', MODX_BASE_URL . 'manager/');

define('MODX_ASSETS_URL', MODX_BASE_URL . 'assets/');


Check your paths to your case. Now let's create the script that will parse our XML schema and creates its PHP representation. Create a file /www/doodles/_build/build.schema.php:

the
<?php
require_once dirname(__FILE__).'/build.config.php';
include_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx= new modX();
$modx- > initialize('mgr');
$modx- > loadClass('transport.modPackageBuilder',",false, true);
echo '<pre>'; /* used for nice formatting of log messages */
$modx- > setLogLevel(modX::LOG_LEVEL_INFO);
$modx->setLogTarget('ECHO');

$root = dirname(dirname(__FILE__)).'/';
$sources = array(
'model' = > $root.'core/components/doodles/model/',
'schema_file' = > $root.'core/components/doodles/model/schema/doodles.mysql.schema.xml',
);
$manager= $modx->getManager();
$generator= $manager->getGenerator();

if (!is_dir($sources['model'])) { $modx- > log(modX::LOG_LEVEL_ERROR,'Model directory not found!'); die(); }
if (!file_exists($sources['schema_file'])) { $modx- > log(modX::LOG_LEVEL_ERROR,'Schema file not found!'); die(); }
$generator- > parseSchema($sources['schema_file'],$sources['model']);

echo 'Finished.';
exit();


You can now run _build/build.schema.php. I do this by loading into a web browser: http ://localhost/doodles/_build/build.schema.php. Then there are the generated class files and maps.


Now, let's make a small adjustment to our base class Doodles (/www/doodles/core/components/doodles/model/doodles/doodles.class.php). Add in the class constructor array_merge immediately after this line:
the
$this- > modx- > addPackage('doodles',$this->config['modelPath']);

It says xPDO that we want to add xPDO package "doodles" that will allow us to make requests to our user table.

the Snippet include

Earlier we created the snippet. To use it you can now create a snippet in the management system and paste the code from the file doodles/elements/snippets/snippet.doodles.php. But this code snippet may not be practical to edit. To fix this you can create a simple generic snippet "include". Go in the control system "Elements" - > "Snippets" - > "New snippet".

snippet Name:
include

snippet Code (php):

the
<?php
return include $file;


Now on any page or in any template you can make a call to the snippet:
the
[[!include? &file=`[[++doodles.core_path]]elements/snippets/snippet.doodles.php`]]


Creating DB queries

First we need to create a table. To do this, just add our snippet to return a value (return) the following lines:
the
$m = $modx->getManager();
$created = $m->createObjectContainer('Doodle');
return $created ? 'Table created.' : 'Table not created.';


Now, if you run the snippet in the database table will be automatically created component "Doodles". You can then remove this code, but better to do so:
the
$tablexists = $modx->query("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '".$modx- > getOption('dbname')."' AND table_name = '".$modx- > getOption('table_prefix')."doodles'");
if(!$tablexists->fetch(PDO::FETCH_COLUMN)){
$m = $modx->getManager();
$created = $m->createObjectContainer('Doodle');
}


Now add in the snippet the following lines:
the
$doodles = $modx- > getCollection('Doodle');
$output = count($doodles);


The output you should see "0" since the table is still empty.

Create the table a couple of rows (for example with phpMyAdmin) and you will see how the snippet displays them. Also the rows in the table to create:
the
$doodle = $modx- > newObject('Doodle');
$doodle->fromArray(array(
'name' => 'TestDoodle',
'description' => 'A test doodle'
));
$doodle->save();


Don't forget to remove this code.

Great! Custom query to the database works! Let's make it more complicated. We can use xPDOQuery xPDO to create some pretty complex queries. Now, let's just add the sort command:

the
$c = $modx- > newQuery('Doodle');
$c- > sortby($sort,$dir);
$doodles = $modx- > getCollection('Doodle',$c);


Sorting happens on the field $sort in $dir. The values of these variables we defined above. In the call to the snippet it would look like this:
the
[[!include? &file=`[[++doodles.core_path]]elements/snippets/snippet.doodles.php`&sort=`name`&dir=`DESC`]]


getChunk Method in class Doodles

Add to the base class a couple of helper methods:
$chunk = null; if (!isset($this->chunks[$name])) { $chunk = $this->_getTplChunk($name); if (empty($chunk)) { $chunk = $this- > modx- > getObject('modChunk',array('name' => $name)); if ($chunk == false) return false; } $this->chunks[$name] = $chunk- > getContent(); } else { $o = $this->chunks[$name]; $chunk = $this- > modx- > newObject('modChunk'); $chunk- > setContent($o); } $chunk- > setCacheable(false); return $chunk- > process($properties); } private function _getTplChunk($name,$postfix = '.chunk.tpl') { $chunk = false; $f = $this->config['chunksPath'].strtolower($name).$postfix; if (file_exists($f)) { $o = file_get_contents($f); $chunk = $this- > modx- > newObject('modChunk'); $chunk- > set('name',$name); $chunk- > setContent($o); } return $chunk; }

Yet, all you need to know what these methods will look for chunks in your directory /www/doodles/core/components/doodles/elements/chunks/.

Create the chunk file /www/doodles/core/components/doodles/elements/chunks/rowtpl.chunk.tpl with approximately the same soderjaniem:
the
<li><strong>[[+name]]</strong> - [[+description]]</li>


In our snippet, add the following lines:
the
foreach ($doodles as $doodle) {
$doodleArray = $doodle->toArray();
$output .= $dood->getChunk($tpl,$doodleArray);
}


The complete code snippet was as follows:
the
<?php

$dood = $modx->getService('doodles','Doodles',$modx->getOption('doodles.core_path',null,$modx->getOption('core_path').'components/doodles/').'model/doodles/',$scriptProperties);
if (!($dood instanceof Doodles)) return ";

/* setup default properties */
$tpl = $modx- > getOption('tpl',$scriptProperties,'rowTpl');
$sort = $modx- > getOption('sort',$scriptProperties,'name');
$dir = $modx- > getOption('dir',$scriptProperties,'ASC');

$output = ";

$tablexists = $modx->query("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '".$modx- > getOption('dbname')."' AND table_name = '".$modx- > getOption('table_prefix')."doodles'");
if(!$tablexists->fetch(PDO::FETCH_COLUMN)){
$m = $modx->getManager();
$created = $m->createObjectContainer('Doodle');
}

$c = $modx- > newQuery('Doodle');
$c- > sortby($sort,$dir);
$doodles = $modx- > getCollection('Doodle',$c);

foreach ($doodles as $doodle) {
$doodleArray = $doodle->toArray();
$output .= $dood->getChunk($tpl,$doodleArray);
}

return $output;


The output will see a list of our test data from the table:


So, we loaded the base class for paths created in the system settings got xPDO package from a custom database table and took him by salonku with the help of chunk.

next part this tutorial talks about creating an administrative page (component). About it on habré recently a good told bezumkin. third part, in my opinion no one has yet written, perhaps I will do the translation. It tells all about the packaging component in the pack which will easily install through the "Manage packages" in the control system.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

mSearch: search + filter for MODX Revolution

Emulator data from GNSS receiver NMEA

The game Let's Twist: the Path into the unknown