Personal notes on Implementing autoPublishing mechanism to custom DB table within modx3.

A need.

  1. I have a custom database setup inside my modx3 install. Recently I started to think of adding a auto-publishing feature for this database, just as the modx3’s siteContent table is capable of doing.

Pre-requisite and assumptions.
1. MODX3 installation
2. Custom database table inside the MODX installation, which is created from modx database schema. I used migx’s package manager to create/modify one.
3. Let’s say that the class name of this dbtable is /MyDB/Model/Mydb
3. A custom table schema for modx to construct the table and associated code structure needs the following fields added to the schema. Note the data type for those date fields are all datetime. and not int(20) like original siteContent table fields for page resource. The difference will be dealt in the implementation code.

<field key="published" dbtype="tinyint" precision="1" attributes="unsigned" phptype="integer" null="false" default="0" />
<field key="publishedon" dbtype="datetime" phptype="datetime" null="true" />
<field key="pub_date" dbtype="datetime" phptype="datetime" null="true" />
<field key="unpub_date" dbtype="datetime" phptype="datetime" null="true"/>

4. The auto-publish mechanism will mimic the existing code for modResource autopublishing mechanism. This reference code lives in core\src\revolution\modcachemanager.class.php. (for MODX2, the file is in core\model\modx\ folder)

5. The function I want is to toggle the record’s published flag based on records’ pub_date and un_pub date. It is still my implementation code (snippets) to filter records based on this flag to display data in the web site.

Two code segments have been written. The first is auto publishing functionality. The code heavily borrows from the aforementioned built-in autopublishing code, which is for page resources.

<?php
/**
 * @var $modx object
 * this code first check if auto_publish.cache.php returns the timestamp that is smaller than now timestamp.
 * if so, then there is at least one Mydb record that need to be acted on (either publish or unpublish))
 * and calls autopublish_mydb().
 */
/* first, check the cache value and compare it to current time */
$options = array(XPDO::OPT_CACHE_KEY => 'my_db'); 
//With this option, cache file is in /core/cache/my_db folder

$actionNeeded = $modx->cacheManager->get('auto_publish', $options);
$timeNow = time();
//No action is taken if cached timestamp is zero or bigger than today's timestamp, except when timestamp does not exist (null)
if (($actionNeeded>0 && $actionNeeded < $timeNow)  || $actionNeeded===null) autoPublish_mydb($modx, $options);

/** * @param $modx
 * @param array $options
 * @return void
 * this function check the Mydb table and alter record's published status.
 * if pub_date is less than current timestamp, and published is false then the record will be published and publishedon data
 * updated, and pub_date will be set to null
 * if unpub_date is less than current timestamp, and published is true then the record will be unpublished and publishedon date
 * and unpub_date will be set to null
 * record is scanned for new unpub/pub_date and cache file is refreshed.
 */
function autoPublish_mydb($modx, array $options): void
{

    $tblResource = $modx->getTableName('MyDB\Model\Mydb');

    $timeNow = time();  //get timestamp number of current date/time

    /* publish and unpublish resources using pub_date and unpub_date checks */
    $modx->exec("UPDATE $tblResource SET published=1, publishedon=pub_date, pub_date=NULL WHERE pub_date IS NOT NULL AND unix_timestamp(pub_date) < $timeNow");
    $modx->exec("UPDATE $tblResource SET published=0, publishedon=NULL,unpub_date=NULL WHERE unpub_date IS NOT NULL AND unix_timestamp(unpub_date) < $timeNow");

    /* update publish time file */
    $timesArr = array();
    $stmt = $modx->prepare("SELECT MIN(unix_timestamp(pub_date)) FROM $tblResource WHERE published = 0 AND unix_timestamp(pub_date) > 0");
    if ($stmt->execute()) {
        $value = $stmt->fetch(PDO::FETCH_NUM); // MIN operator query will return only one row. query is asking for only one column
        if ($value[0]) $timesArr[] = $value[0]; // that column is either empty or value. If value exists, push into array.

    }
    $stmt = $modx->prepare("SELECT MIN(UNIX_TIMESTAMP(unpub_date)) FROM $tblResource WHERE published = 1 AND UNIX_TIMESTAMP(unpub_date) > 0");
    if ($stmt->execute()) {
        $value = $stmt->fetch(PDO::FETCH_NUM);
        if ($value[0]) $timesArr[] = $value[0];

    }

    $nextEvent = (count($timesArr) > 0) ? min($timesArr) : 0;//select younger of timestamps if these exist. Otherwise, $nextEvent=0;
    /* cache the time of the next auto_publish event */
    $modx->cacheManager->set('auto_publish', $nextEvent, 0, $options);
}

Some points to note.

  1. dbtype for these dates are datetime. In original code, they are integer(timestamp).   MySql function unix_timestamp() is used inside the query to  convert them as needed.
  2. The function return type is changed from array to void. I thus removed all return values construction as I could not think of any use case in my usage scenario.
  3. This code, when invoked will fetch the cache file /core/cache/my_db/auto_publish.cache.php. The file will return a timestamp value or 0 or null. The location of the cache file is defined in $options array and separate from the system’s auto publish cache file.
  4. if timestamp returned is less than current timestamp but not 0 or timestamp returned is null (meaning there is no cache file) then it proceeds to auto_publish function Otherwise it will return without doing anything.
    1. Auto publishing function will do the following. They are basically two pdo queries.
      1. Publish all record that’s pub_date is less than current time, unset pub_date, set publishedon to pub_date.
      2. Unpublish all record that’s unpub_date is less than current tme. unset unpub_date and unset publishedon date
      3. set new cache value to minimum of remaining unpub_date and pub_date sets.
    2. In the original code, Unpublishing query is resetting both unpub_date and pub_date. I can not see any side effects on not unsetting pub_date so I left it out.

Now, when to invoke this code? The original Resource auto_publishing code is invoked whenever resource is accessed.

I originally added the code as a snippet to my site’s landing page so that the code will execute whenever somebody visit my site. But I also wanted to tryout the plugin functionality, so I saved the code as a plugin and named the plugin refresh_pub_date

Then in the system’s settings, I created a custom event. myCustomeEvent and enabled the refresh_pub_date plugin to this event.

The following code will invoke this event. So my snippet in the landing page is just that.

$modx->invokeEvent('refresh_pub_date',array())

Second code is used to check for pub_date, unpub_date after record save/update

<?php
/**
 * afterSave snippet for Mydb (migxdb configurator setup  'snippethook')
 *  the snippet will update auto_publish timestamp for the custom table
 * if unpub or pub date is smaller than the value already cached.
 *
 * migx provides the following variables
 * @var $modx object modx runtime object
 * @var $object object  object of the class being handled
 * @var $postvalues  array of posted values
 * @var $scriptProperties  mixed parameters, Includes all of the above.
 */

$result = '{"error":"","result":"success"}';
$options = array(XPDO::OPT_CACHE_KEY => 'my_db'); //define folder name for cache file
$timesArray = [];
$pub_date_ts = strtotime($postvalues['pub_date']); //retrieve pub_date datestamp
// the value is also retrievable from $object.  ie., strtotime($object->get('pub_date'));
if ($pub_date_ts) {
    $timesArray[] = $pub_date_ts;
} //push pub_date to times array if exists. boolean evaluation of the variable returns
// false if $pub_data_ts is empty, null, 0 or false

$unpub_date_ts = strtotime($postvalues['unpub_date']);
if ($unpub_date_ts) {
    $timesArray[] = $unpub_date_ts;
} // push unpublish date timestamp to the times array if exists

$current_ts = $modx->cacheManager->get('auto_publish', $options);
if ($current_ts === null and count($timesArray) == 0) {//In case of no cache file, meaning  the file is manually deleted somehow.
    $current_ts = time(); //set the time so auto_publish logic will run once and will set correct time if pub/unpub already existed.
    $modx->cacheManager->add('auto_publish', $current_ts, 0, $options);
}

if (count($timesArray) > 0) { //rewrite timestamp only if new TS is younger or the  current one is 0. Don't write otherwise
    $new_candidate_ts = min($timesArray);
    if ($new_candidate_ts < $current_ts || $current_ts === 0) {
        $modx->cacheManager->replace('auto_publish', $new_candidate_ts, 0, $options);
    }
}
return $result;

This code will be invoked when Mydb record is added/updated. Since I use migxDB to create a CMP for this DB table in the manager, migxDB custom configurator allows the easy way to attach the snippet to after save action.

{"aftersave":"mySnippet"}

If called this way, migxDB provides $postvalues array variable to allow access to posted data field that is needed to be convert to timestamp like so.

strtotime($postvalues['unpub_date']);

It also provides $object variable for the class object (including record fields that are not posted) So the same value can be retrieved with usual get method.

strtotime($object->get('unpub_date'))

Note: If I don’t use migxDB CMP configurator and write my own class processors extended from MODX processors, retrieval of the same value in aftersave hook in the update processor would have been

strtotime($this->getProperty('unpub_date'))

There was a bonus take away from this project

  1. I learned how to use MODX Cache file system. The main purpose of using the cache files must be obviously to eliminate a need of re-running of various codes on server side. However I can also do things like the following to record values of variables into the cache file for debugging purpose.

$options=array(XPDO::OPT_CACHE_KEY=>'debugstore');
$myvalues=print_r($varToCheck,true);
$modx->cacheManager->set('myVarvalues',$myvalues,0,$options);

array_reduceの話2

array_reduceと arrayのsplat オペレーターを使うと、同じアレーを生成するという意味のない関数ができる。

$source_array=[3,2,4,5];
$nop_array=array_reduce($source_array, fn($carry,$item)=>[...$carry, $item],[]);
print_r($nop_array); //array=>[3,2,4,5]

で、パターンを使って、array_mapもarray_filterもarray_reduceで代用できるというお話



// array reduce to mimic array_map
$source_array=[3,2,4,5];
$double=function($x){
    return $x*2;
};
$mapped_array=array_reduce($source_array,fn($carry,$item)=>[...$carry,$double($item)],[]);

print_r($mapped_array); //array=>[6,4,8,10]

//array reduce to mimic array_filter

$even_fn= function($x){ //return true if even
   return ($x % 2)==0;
};


$filtered_array=array_reduce($source_array,
    fn($carry,$item)=>$even_fn($item)?[...$carry,$item]:$carry,
    []);


print_r($filtered_array); //array=>[2,4]


めでたし、めでたし

array_reduceの話

PHPでプログラミングするとき、例えば、以下のようなアレィがあったとする

$fields=array("a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D")

この内容をキーとともにリストアップしたいとき、まず思いつくのがforeachを使ってこう書く方法

$output="<h3>listing array with foreach</h3>";
foreach($fields as $key=>$value){
    $output.="<pre>".$key.":"$value."</pre>"
}
echo $output;

ただ、このアレィの複数エレメントを一つの値(この場合は一つの文字列)にまとめるという風に考えるとarray_reduceという関数を使っても同じことができる

$output="<h3>Listing array with array_reduce</h3>";
$output=array_reduce(array_keys($fields),fn($curry,$item)=>$curry."<pre>".$item.":".$fields[$item]."</pre>",$output
);
echo $output;

この関数、単純なArray処理は問題ないとして、Associative Arrayを処理するとなると、はたと困るが、渡すArrayを値ではなく、キーを渡すことで可能となる。これは一つ目の因数にarray_keysを使うことで実現できる。コールバックには因数が二つ付くが、慣習として$curry と$itemという変数名を使う。for 文に$iをつかっているのと同様で、もちろんほかの変数名としても問題ない。$curryは結果を集積していく入れ物。$itemはアレィの各エレメント。上の例ではコールバック関数はアローファンクションを使ってインラインの書き込み。 三つ目の因数($output)は初期値

このarray_reduceの使いかたをネットの検索などで探すと、数字を加算していくような例がよく紹介されているが、上のように文字列アレィの加工にも有効。 さらに、このアレィは関数のアレィでもよいわけで、例えば、複数の関数で同じ文字列の処理を繰り返すような場合、例えば、文字列をトリムしあとhtmlにエンコードしたい場合など、つまり同じ文字列に何度もパスをかけて加工したい場合など

$output=trim($output);
$output=htmlspecialchars($output);
//または
$output=htmlspecialchars(trim($output));

のかわりに

$output= array_reduce([trim,htmlspecialchars], fn($curry,$item)=>$item($curry),$output)

と書いてしまう技がある。関数が二つなら最初のアプローチのほうがよさそうだが、かかわる関数が増えてくると、コードのメンテナンスは下のパターンのほうがすっきりするよ、とFunctional Programmingの推進者たちはいっているが、さてどうする。

$functionsArray=[fn1,fn2,fn3,fn4,fn5]; //同じオブジェクトに対し、5つの関数処理をおこないたい
$result=array_reduce($functionArray, fn($curry,$item)=>$item($curry),$initialValue);

“Why did I not think of this?” moment in MODX 3 snippet development with PHPStorm

This is one of those ‘Aha!’ moments.

MODX is one of CMSs (content management system) that I have been using for a long time to maintain my shogi site. Market penetration of this CMS is said to be 0.1%. As of this writing, 43% of of Web sites uses WordPress and no other CMS reach even 10% of market share. (source, w3techs.com . I actually think MODX penetration is much greater than this number, MODX does not leave big footprint to show it’s identity in front of web crawlers. -Many sites I know powered by MODX, including mine – were identified as non-CMS site by w3tech’s tool.— Nevertheless, there is no doubt WordPress dominates the CMS market)

Although both uses same language (PHP) for powering the site, MODX gives much more flexibility in site design but you need to be proficient with HTML and CSS (and JavaScript). In another word, MODX is a tool while WordPress is a product.

What I liked about MODX over WordPress at the time was a separation of PHP code and HTML. This was a long time ago so the situation might have changed on WordPress, but I have been happy with MODX since.

At the time, I also looked at Drupal and Joomra . All of them forces you to follow their interpretation of what website should be. MODX has no opinion on how you want to create Web site. You have a total freedom.

On the other hand, if the only thing you want to do with your site is blogging, there is no better solution than using WordPress.

I digressed. Back to the subject!

With a wealth of plug-ins available, your rarely need to code in PHP. When you do though, MODX separates a PHP coding part from HTML with a thing called Snippets. A snippet is then inserted in to HTML with a use of tag.

[[aSnippet]]

You can edit a snippet within MODX backend’s editor. With a plugin like ‘ACE’ there is a nominal amount of syntax checking to the PHP code. Unfortunately, I am being spoiled with convenience of IDE such as PHPStorm,

MODX backend, Snippet editor with ACE plugin

So there is a desire to be able to write a snippet code using PHPStorm and get benefits of auto-completion and methods hinting, strict error checking and warning.

Unfortunately, there is no plugin in PHPStorm for MODX or MODX plugn for PHPStorm.

When I google “MODX with PHPStorm”, there are several hits, Although none of them gave me a direct answer on what I wanted to do, I was able to put enough facts together and came up with a solution. The solution that was just waiting for me to assemble few known facts, that I have used all of them separately in the past!


1. Fact that PHPstorm can create a project using existing remote site contents, so that PHPStorm can pull all file setup from MODX site.

PHPStorm->File->New Project from Existing Files


2. Fact that PHPstorm can then start syncing file contents of my local project files to server files.

File sync options available in PHPStorm

Ok, but Snippet is stored in Database. the code is not in the file, but then,

3. Fact, MODX snippet code can be from a static file, meaning I can create a PHP file and feed it as a snippet to MODX by turning on “is Static” option and point to the file location

Is Static option is turned on for Snippet. Snippet is now fed from “static file”



Result: When I combine those 3 factors, I can create a snippet with a comfort of PHPStorm IDE environment.

Snippet now editable in PHPStorm
Snippet output on browser page, powered through MODX

What do I gain? Full context support and code hinting, as PHPStorm analyzes whole MODX site and figures out all variables and object and its methods being used inside MODX. I have to give credit to MODX development team for fully documenting source files. Thank you!

PHPstorm explains what getOption method is
PHPStorm autocomplete support in action.

You can also open the corresponding file by pointing to method and do control-b to drill down on function.

I edit a file in PHPStorm, and you either 1) turn on auto-sync between your local files and server files or 2) manually upload changed file to server. Then I can run and test the snippet with MODX backend immediately.

So all of the sudden, I feel like invincible. It will be very hard for me to make coding error. (Ok, logic error still possible)

Once Snippet development is done, you may switch off the ‘is static’ option and keep the snippet inside database, remove the php file from the system.

One thing that is kind of annoying is that by default, PHPstorm thinks $modx variable is undefined. The default setting on PHPStorm’s inspections on undefined variable is somewhat on conservative side. It will not even acknowledge variables from included file!. To mitigate this, you need to put a check on “Search for variable’s definition outside the current file”

PHP settings->Editor->Inspection->PHP->Undefined symbols->Undefined variable

It hurts to think that I have been using both PHPstorm and MODX for more than 10 years and I am figuring this out just now 🙁

factory function?

JavaScriptやPHPで、Function Factoryという言葉を見るのだが、これは共通するパターンのFunctionを生成するためのFunctionということで、例えば

$double = function($x){
    return $x*2;
};

$triple = function($x){
    return $x*3;
};

$quadruple = function($x){
     return $x*4;
};

なんてのは、三つのfunctionを定義していても、よく見ると、2,3,4とかける数字が違うだけで、あとはまったく同じパターン。 このようなfunctionの記述を効率よく行うために

// This is function factory
$create_multiplier = function($y){
    return function($x) use ($y) {
        return $x * $y;
    };
};
//use the function factory to create three functions.
$double = $create_multiplier(2);
$triple = $create_multiplier(3);
$quadruple = $create_multiplier(4);

というような書き方をするテクニック。肝心な部分の記述は一回で済むためコードの管理も簡単になる。

上のコードは返すコードのなかで、$xを認識させるため、 use ( $x )というクローズをいれているが、PHP7.4 からPHPでもアローファンクションが使えるようになり、これだとuse というkeyword無しで、外側で設定されている変数を認識するようになる。ので、

$create_multiplier = function($y){ return fn($x)=>$x*$y;};

//use function factory to create three functions.

$double = $create_multiplier(2);
$triple = $create_multiplier(3);
$quadruple = $create_multiplier(4);

と、ファクトリーの部分の記述がすっきりする。

さらにこの上位のファンクションもアローファンクションで書いてしまうと

$create_multiplier = fn($y)=>fn($x)=>$x*$y;

となり、 プロのコーディングでアローが二つも三つもかさなるような記述が時々でてくるのだが、そろそろ読解がしんどくなってくるので、後で読み返してわかる記述方法としては、よし悪しかなあ。

困ったときはログファイル

Cubieboard2 (Arm7 Cortex Dual core 1GHz, 1GByte Ram, 4GByte Flash, OS=armhf debian 7) という、約60ドルで中国から購入したデベロッパーボードを使って運営しているホームサーバーの管理にISPConfig3という管理ソフトを導入して使用している。 Mailサーバー、Webサーバー、DNSそしてnginxプロキシなどをWEBブラウザから一括管理できるので重宝している。
このツール、HowtoForgeというサイトでサポートされていて、”Perfect Server” というシリーズで多様な組み合わせのサーバー設定のチュートリアルが掲載されている。 たとえばこの記事はOSはDebian,サーバープロキシにnginx, mailにはDoveCot、DNSにはBindを使ったサーバー構築の手順が述べられており、実は自分のサーバーはこれを参考にして立ち上げた。(DNSとNGINX、PHP、MySQLの部分はともかく、メールサーバーとメールフィルターの設定は自分ひとりの能力では手も足も出なかった。きちんと設定できたのは上の記事のおかげである)
 最近バージョンのアップデートをしようと思い立ち、コントロールパネルに指示されているiSPsonfig-update.shというコマンドを実行してみた。  アップデート自体はまったく問題なく行えたのだが、 コントロールパネルにログインしようとしてみたところいつもログイン画面が出るはずのurlが空っぽのページになっている。 
他のサービス(メール、Webサイト、DNS、SSHなど)は普通に動いているので、IPSConfigのダッシュボードへのログインが表示できないという限定された不具合だ。 幸いなことにISPConfigのUpdateツールがまず実行するのは旧バージョンのバックアップなので最悪の場合はリストアを行えばよいが、ページのヘッダー情報を見てみると、エラー番号が500、Internal Server Errorとなっているので、これはPHPの実行時、ページレンダリングの途中でおかしくなっている、とあたりをつけた。 NginxのErrorLogを眺めてみると大当たり、Fatal Errorが記録されていた。

11:54:25 [error] 3452#0: *528 FastCGI sent in stderr: "PHP message: PHP Warning:  require_once(/usr/local/ispconfig/interface/lib/config.inc.php): failed to open stream: Permission denied in /usr/local/ispconfig/interface/web/index.php on line 31
PHP message: PHP Fatal error:  require_once(): Failed opening required '../lib/config.inc.php' (include_path='.:/usr/share/php:/usr/share/pear') in /usr/local/ispconfig/interface/web/index.php on line 31" while reading response header from upstream,云々

上のエラーメッセージはindex.phpの31行目config.inc.phpを読み込もうとしたところ、拒否されました。と言っている。 ../libr/config.inc.phpのアクセスレベルがきつすぎるようだ、。
そこでこのファイルを調べてみるとOwner,GroupともISPCONFIGとなっているのは良いとして、Ownerのみに読み書き権限があたえられており、Groupレベルで読み書き禁止となっている。  それではと、Groupレベルでの読み出しも許可に書き換えてみたところ、あっさりと復活した。

#  chmod g+r config.inc.php

これが普通とは思わない。何らかの理由でサーバーのワーカープロセスとファイルのオーナー情報が背反しているためにファイルアクセスできなくなっていると考える。 少し思い当たることがあって、同じサーバー内部でModxを使ったサイトを運営しているのだが、アップデートするたびにSetupフォルダーをマニュアルで削除しなくてはならない、という作業が発生している。 Setupフォルダーの削除はインストール画面でチェックをいれてやれば自動的に行ってくれるはずなのだが、このサーバーではそれができてない。

というわけで、本日わかったこと。
1. Linuxのアクセスレベル管理がいまいち理解できてないなあ。
2. Linux 不具合解析はまずError Logを読む事。

NGINX ん、じんくす? 違わい、エンジンXだい!

nginxと書いてエンジンエックスと読むそうである。webサーバーとして動くことは勿論だが、その他, リバースプロキシとしてはWEBのみならず、mailも扱えるということなので、 今回raspberry piにインストールしてみた。 徐々に使い方を覚えていき、他のサーバーからの移行を画策していこうと思う(狙いは省電力化)が、とりあえずはPHPおよびmysql を一緒に実装し Lamp ではなく、LNMPとして動かすことをやってみる。 Wikipediaの解説によれば非同期のイベント駆動モデルを使っており、プロセス・スレッド駆動のapacheなどのサーバーに比べると高負荷になっても動きが鈍くならない、という利点があるらしい。なんとなくraspberry pi にぴったりのサーバーのような気がしてきたが、実際は如何に。

実装後のテスト画面は以下。ごらんのとおり、PHPが実装でき、phpmyadminが動いている、という証拠スナップ。

phpinfo()をRPi上で実行。

phpinfo()をRPi上で実行。クリックすると拡大画面になり、raspberry piのバージョン情報などが見える。

以下のconfig file を参照。

以下のconfig file を参照。

いくつかネット上の記事を参照したが、メインはこちらの記述に従った。 上記サイトのコピー記述になるのでどうかとも思うが、とりあえず実行したコマンドだけ羅列すると以下になる。
なお、ヘッドレスでアクセスをしているので、入力はすべてsshによるリモートターミナル経由だ。

mysql-server をまずインストール

sudo apg-get install mysql-server [Enter]

mysql のルートパスワードを要求されるので入力したパスワードは忘れないようにしよう。

次にnginx本体をインストール。 ところで、サイトによっては、「ディストロについてくるnginxは0.6とえらく古いのでソースを落としてきてコンパイルしよう、」とその方法を詳しく説明してるところがあったが、apt-get update; apt-get upgrade 後にインストールしたバージョンは1.2.1であった。 ソースをみるとすでに1.51がリリースされているが、ものすごく古い、というわけでもなかったので、パッケージ品をそのまま使うことにする。 自分も齢60歳。いちいち凝っている時間はもうないので次に次にと進むのである。

sudo apt-get install nginx [Enter]
sudo service nginx start [Enter]
sudo apt-get install php5-fpm [Enter]
sudo apt-get install php5-imagick [Enter]
sudo apt-get install php5-curl [Enter]
sudo nano /etc/php5/fpm/php.ini [Enter]

最後のコマンドではnano editor が起動するので、Ctl-W で、以下の部分を探し、修正。
“upload_max_filesize=2M” => “upload_max_filesize=10M”
Dynamic Extentions のセクションに 以下を追加

extension = imagick.so

あとはCtrl-O でセーブし、Ctrl-x で終了。

phpmyadminをインストール

sudo apt-get install phpmyadmin [Enter]

Web-server selection の画面でApache か lighttpdかどちらのサーバーかと、と聞いてくるが、どちらでもないので、チェックを外しておく。 次の画面でパスワードを設定。

sudo service php5-fpm restart

以上すべてsudo で暫定ルート権限で実行しているが、面倒であれば、sudo su でルートになれば、sudoは省ける。

ここまでで必要なサービス(nginx,mysql,PHP5)はすべて導入された。 ちなみにこの状態でRPiのIPアドレスをブラウザで見ると”Welcome to NGinx”という簡単なメッセージが出て、nginxが無事動いているということがわかる。

ただし、この状態では静的なファイルしか表示しない。 PHPファイルを認識させるにはconfig file をいじる必要があるが、その前にサイト用のダイレクトリを設定しておく。
ルートフォルダにある/srvにフォルダーツリーを作っていく.

sudo mkdir /srv/www [Enter]
sudo chown -R USER1:www-data /srv/www [Enter]
sudo chmod -R 755 /srv/www [Enter]
mkdir /srv/www/site1 [Enter]
cd /srv/www/site1 [Enter]
mkdir logs [Enter]
nano logs/access.log (ファイルを作るだけなので、カラのファイルをセーブして即クローズ)
nano logs/error.log (ファイルを作るだけなので、カラのファイルをセーブして即クローズ)

上でやっているのは、nano で空のファイルを二つ作るということだが、空のファイルを作る技としてはほかに

touch filename

さらには

> filename

なんてのもある。最後のコマンドは無入力をfilenameにリダイレクトする、という技で、20年前に感心して覚えたのをすっかり忘れてた(爆)


mkdir public [Enter]

上の例では自分はUSER1というユーザー名でログインしてると仮定し, /srv/www/というフォルダーにsite1というフォルダーを作成する。 srv/www/site1/pulicを、作ろうとしているサイトのルートとしてここにWeb 構造を展開して使う魂胆である。ホストできるサイトは一つだけではないので、その場合はsite2, site3など、任意名のフォルダーを作って割り当てていけばよい。
次にCONFIG FILE で上の情報をつなぎ合わせるわけだ.
デフォルトのCONFIG FILE は /etc/nginx/nginx.conf というファイルで、これはすでに存在している。 ネット上の多くの説明では、このファイルを直接いじるような印象だが、 今回インストールされたnginx.conf を見てみると、ほとんどコメントアウトされていてコメントを抜くと、フレームワークがあるだけで、ほとんど何も実際のコマンドになっていない。 ただ、webサーバーを指定しているhttpブロックをよく見てみると 以下の記述があることに気付く。

include /etc/nginx/sites-enabled/

で、このsites-enabled のフォルダーを眺めてみると、default というファイルを見つけたわけだが、このファイルは実体のないソフトリンクされているポインタであり、実際のファイルは/etc/nginx/sites-available/default というファイルだったりする。

このsites-available, sites-enabled というフォルダーはapache2 でもおなじみで、サイトの構成ファイルをsites-available のほうに、one site= one file 単位でセーブしておき、 有効にしたいサイトのみ、ソフトリンクでsites-enabledにソフトリンクする、という仕組みになっているようだ。 ”welcome to nginx”というメッセージはこのdefault の中に以下の記述がある。

root /usr/share/nginx/www;
index index.html index.htm;

これは「このサイトのルートページはローカルマシンの/usr/share/nginx/www フォルダーですよ、html とhtmのエクステンションを探して表示しましょうね、」と言っている。 ちなみに、このフォルダーをみてみると、index.htmlというファイルがおいてあるので、その中身をみると、やはり”welcom to nginx”とhtml で書いてあるわけだ。
今回は/srv/www/site1/public/をルートにしようとおもっているので、別のconfig ファイルをつくり、そのファイルをsites-availableにセーブし、ソフトリンクでsites-enableに登録するという作業を行う。 ファイルの名前をsite1として話を進める。

sudo nano /etc/nginx/sites-available/site1 [Enter]

実際のファイルの中身は以下のようになる。

server {
access_log /srv/www/site1/logs/access.log;
error_log /srv/www/site1/logs/error.log;
root /srv/www/site1/public/;
location / {
index index.php index.html index.htm;
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /srv/www/site1/public$fastcgi_script_name;
}
location /phpmyadmin {
root /usr/share/;
index index.php index.html index.htm;
location ~ ^/phpmyadmin/(.+\.php)$ {
try_files $uri =404;
root /usr/share/;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ {
root /usr/share/;
}
}
location /phpMyAdmin {
rewrite ^/* /phpmyadmin last;
}
}

このファイルをソフトリンクするついでにdefaultをsites-enabled から削除(リンクを解除) さらにサービスを再スタート

sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1
sudo rm /etc/nginx/sites-enabled/default
sudo nginx restart

まだルートフォルダにはページをつくっていないので、このままでは何も表示しない。 phpinfo() というphp関数を実行するだけのファイルを作成してルートフォルダーにセーブする。

nano /srv/www/site1/public/index.php

以下の一行を記述

<php? phpinfo(); ?>

ctl-o で書き込み、ctl-x でクローズ

キーボード、モニター付きのRPiなら、あとは”localhost”とブラウザに打ち込むわけだが、外部のマシンからのブラウザアクセスをする場合にはIPアドレスを入力してやる必要がある。自分の環境ではローカルマシンでbind9というDNSをうごかしている。ここに192.168.1.97というローカル固定IPをraspberrypi.home.lanというドメインネームとして登録した。早速これを使って http://raspberrypi.home.lanと入力すると一番上の画面が出てきた。 さらに http://raspberrypi.homel.lan/phpmyadmin と入力してphpMyadminのログイン画面が出た。 このアプリはPHPで動くので、PHPの作動確認OK. さらに ログインしてmysql のデータベースもちゃんと使えることを確認して本日のところは終了。 config の部分についてはnginx.org/document/ のビギナーズガイドが非常に参考になった。
外部からのアクセスができるようにするにはserver_name などのパラメータを追加していく必要があるはずなので、今後ぼちぼち試行錯誤していこうと思う。理屈としてはこれでWordPress などのCMSも導入可能になったはずだ。

MyBook LiveというNASでWEBサイトをホストしてみた。実践編その3

一昔前と違い、WordPressやJoomla!あるいはDrupalとかModXなど Webサイトを作るのに
Content Management System (CMS)を使うのが当たり前のようになっている昨今、 Apacheサーバーにはデータベースサービス(例えばmysql)とこのデータベースとウェッブページをつなぐツール(例えばPHP)が必要だ。 最初から何も実装されていなければ、lamp(linux Apache mysql php)などという便利なパッケージもあるが、すくなくともapacheサーバーは実装されているのでlamplをインストールするわけにもいかない。

Mybook Liveで動いているApache2サーバーはすでにPHPも実装されている。 UIの画面自体、PHPベースのCakeというフレームワークで作成されているようだ。

<?php phpinfo(); ?>
という一行を phpinfo.php というファイル名でWEB folder上に保存し、(前回の例では/stores/Public/WWW) ブラウザでアドレスを mblwebsite/phpinfo.phpと打ち込むと、PHP情報が表示されるはずだ。
これを見るとMysql用のインターフェースも実装されている。 PHP側からmysqlをアクセスする準備は整っているわけだが、 残念ながらMBLはmysqlのサーバーは実装していない。
そこでMysqlサーバーを個人のリスクでインストールすることになる。
Debian上でのインストールはいたって簡単で、
#aptitude update
でパッケージリストを更新したのち、
#aptitude install mysql-server
または
#apt-get install mysql-server
インストールの準備が整うと、本当に実行しますか?と聞いてくるのでYesと応えるときちんと実行される(はず)
途中でパスワードを作るように催促されるのでMysqlのroot 用のパスワードを適当に考えて入れてやる。(覚えておきましょう)
インストールの終わりにはMysqlサーバーを起動してくれるので、 これをアパッチ側にも認識させるために アパッチサーバーを再起動
/etc/init.d/apache2 restart
これでApache +Mysql + PHP の環境が整った。
また、Mysqlの管理用にphpMyAdmin あるいは sqlbuddyなどの管理ツールを用意しておくと楽である。 これらはいずれもパッケージをダウンロードし、Web用のフォルダーにサブフォルダーを作り、解凍しておくだけで、すぐにブラウザーから使えるようになる。 詳細はそれぞれのパッケージのReadme を見てください。

MyBook LiveというNASでWEBサイトをホストしてみた。実践編その1

Mybooklive、サーバー化の第一歩

というわけで、Western Ditigalの廉価版のNAS Mybook LiveをWebサーバー化する方法を
書いておく。
まずはSSHによるターミナルアクセスを アクティブにする。
MybookLiveではGUIの隠れコマンドになっている。
まず、MybookLiveにログインした状態で、WEBブラウザーの”192.168.1.25/UI”などとなっているアドレスバー上のアドレスを”192.168.1.25/UI/ssh” というように”ssh”を追加することで、sshのコントロールパネルが開く。 ここでsshにチェックマークを入れるだけで完了。
他のMybook製品に比べるとかなり手順が簡単になっているらしい。
これでセキュアシェルのログインができるようになったわけで、UbuntuなどのLinux機からのアクセスはターミナルからsudo ssh root@192.168.1.25 などとやるだけでよいのだが、 Windowsからの接続はPuttyというユーティリティプログラムが必要。
上のリンクからPutty.exe.をダウンロードして実行する ホストネームまたはIPアドレスを入力してSSHを指定してOpenするとリモートログインのコマンド画面になる。root ログインでデフォルトのパスワードはwelc0meになっている(英語のオーではなくゼロであることに注目)
ちなみにこれはMybook liveのデフォルト値なので、外部アクセスを許すつもりなら、パスワードは変更しておいたほうがよい。”Unixコマンド:passwd”
ハックサイトではナノというエディターを使うことを推奨している。このエディタはnano filename で起動できるが、同じユーザーネームとパスワード、そしてport 22指定で、sftp接続もできるので、 Filezillaからsftpで接続し、ファイルのエディットはFilezillaのView/Edit機能を使ってWindows環境で行うこともできる。
オプショナル:
いつもrootで接続するのも気持ちわるいので他のユーザー名を追加しておく
useradd user1 -p user1password
しかしこのままではuser1はsshからアクセスすることはできない。/etc/ssh/sshd_config の中にあるAllowUsers directive にuser1を追加する
AllowUsers= root
これを
AllowUsers= root user1
と変更し、ファイルを読み込ませるために以下を実行
/etc/init.d/ssh reload
ところで
sshのデフォルト設定ではパスワード無しのログインはできないようになっているので(sshd.conf中で設定されている)パスワードを空白に指定するとアクセス不能になる。 まさかと思ったがネットの記事を見るとRoot権限のパスワードの設定を空白にしてしまう人もいるらしいので ご注意を。 しっかし root で侵入されて cd / ; rm * とでもやられることをかんがえないのだろうか?
次にはPHPを実装したWEBサーバーは簡単に構築できる、というか、すでに実装されている。 その証拠にIPアドレスを入力してあげるとhttp://mybookliveaddress/UI というアドレスに自動的に飛んでログイン画面になるわけでしょう? これってWebサービスですよ。
FileZilla で、中身をのぞいてみると /var/wwwというところにWEB用のページがおかれていることがわかるので、ここにソフトリンクをはってしまえばよいことになる。
ln -s shares/Public/WWW /var/www/myweb
上の例ではPublic Share にWWWというフォルダーをつくり、これをホームページとしてサーブしてもらう感じ。
他の手段としては フランスのどなたかが、FeaturePackというユーティリティを作ってくれている。
これだとmybooklive:8080 が通常のWEBサイトに、 さらにhttps://mybooklive がメディアサーバーとしてセットアップされるようになっている。
そのうちに説明をポストするけど、いつになるかわからないので、英語に自信のある方は自分でやってみてください。

また、どちらもMySQLデータベースは実装されていないので、WordPress などMySqlサーバーが必要なCMS等を運用する場合には aptitude、あるいはapt-getを使って追加してあげる必要がある。  これもそのうち説明します。(まだ2日しか動かしていないのではっきり作動しているという保障がないので)

Why study PHP? 何故PHPなのか その2

まずは前回の復習 (Internet上のページがどのようにつくられているか、門外漢向けの説明、と同時に自分が理解したと思った内容の復習です)

  1. Internet のWeb home  Page上の情報 は HTMLという言語で記述されている。
  2. 表示の加飾方法(内容ではなく)についてはCSSというファイルに記述され、これがHTMLを記述したファイルから参照されている。
  3. これをブラウザが読み込んで人間たちが読めるように表示する。
  4. HTMLとCSSで構成されたweb Pageは静的なページで閲覧者のアクションに対して動的なページにするにはスクリプト言語を追加する必要がある。
  5. スクリプトにはブラウザー側で動くスクリプトとサーバー側で動くスクリプトがある。

 

と言うところまで説明しました。 今日はここから

ここで言っているサーバーの意味を簡単に言うと世界のどこかにある、Internetでホームページを発信しているコンピューターのこと。 スクリプトがサーバーで実行される場合は、閲覧者のコンピューターに届くときにはごく普通のHTMLファイルになっています。 ブラウザーからはサーバー側でどのような作業がされたのかを見ることはできません。 これの代表的な言語がPHPです。(マイクロソフトのExpress WebやVisual StudioでWebアプリケーションを作っている場合はNET.ASPという言語がサーバー側で動き、アプリケーション(aspxファイル)を処理してhtmlとCSS、VBscriptなどに吐き出してブラウザー側に送っています。)

Client 側とは 閲覧者が使っているブラウザーです。 ブラウザー側で動くスクリプトはHTMLに”このスクリプトを使ってね“という記述があって、 それをブラウザーが実行します。代表的なスクリプト言語にはJavaScriptがあります。VBScriptなどもそうです。 JavaScriptを使うと、dropdownやflyoutのメニューなどや、単語にカーソルを当てると、それに対応したイメージを表示させるような仕組み、フェードイン、フェードアウトのようなアニメーションをページに組み込むことができます。すなわちスクリプトの内容もブラウザー側に送られてきます。 ちなみにこのスクリプトの内容はIE8のような新しいブラウザーでは開発モードにすると(F12を押す)見ることができます。

Server side Scripting, Client side Scripting, それぞれ得手不得手があるので同じページに両方使うことも普通に行われます。

Server side scripting はサーバーがPHPやASP.netなどの環境をサポートしている必要があります。 有料のプロバイダーはほとんどの場合PHPをサポートしていますが、 無料サービスのホストの中にはサポートしていないこともあり、 そのようなHostを使っているときには、プロバイダーが用意したページ作成ソフトで画面を作るか、 HTML,CSS, JavaScriptの三種の神器のみでページ作成に取り組むことになります。

ここで自分がどうしてこのような事に興味を持ったのか、その理由を書いておきます。

インターネットサービスのプロバイダーと契約をしたときに、メールアカウントの他に、HomePageのスペースも頂きました。 15年も前の話です。 当時はwindows 95にFrontpage Expressというhome page 作成用のツールがついてきましたが、 今にして思うと、これは簡単なHTML生成のソフトでした。  このツールだけでも当時としてはそれなりのページが作れましたのでなんとなく満足していましたが、そのうちプロバイダーがWebPageBuilderというブラウザ上で簡単にhomepageを作れるツールを提供するようになりました。 これはテーマを選ぶと、それなりのページが作れ、メニューも自動生成される、というものでした。 それからというもの、内容の編集はすべてWEB上のツールで行いました。 しかし、それだとどうしても出来るページ、構成は定型なものになってしまいます。

私の場合、最初はHTMLだけで簡単なhomepageを作っていました。 そのうち、他の方たちのページを見て、見出し部分がしっかりトップにあり、メニューが左側に配置され、 その右側に本文が並べられているような作りはどうやって実現するのだろうと興味を覚えCSSを覚えることになりました。

さらに簡単なアニメーションとかも入れたくてJavaScriptも少し勉強しました。 JavaScriptの場合、Internetに色々なコードが開示されていたり、さらにJQuery というswiss army knifeのようなツールがあって、 かなり高度なこともそれほど深い知識が無くてもホームページ上で使えるようになります。

これだけでわりと「らしい」ページを作れるようになったのですが その次に困ったのが「すべてのページ共通な部分をいちいち コピーペーストで複製していかなくてはいけない」 ということでした。

特にメニュー関係の項目は メニューを増やすごとにすべてのページを編集しなければなりません。 これはもっと良い方法があるはずだ、と思ったのです。

調べてみると、サーバーサイドインクルード(SSI)という仕組みがあって、HTMLの記述に 「このファイルを含むのよ」 と書き込んで 別ファイルにメニューの記述を書いておけばこの別ファイルを変更するだけで このファイルを参照しているすべてのHTMLページに反映される、ということができるらしい、という事がわかりました。 ただし、この機能はサーバーがサポートしている必要があります。 

SSIと同じことは PHPでもできます。同じ事はJavaScriptでも出来るのですが、 PHPでは 一行のコード  ですむところをJavaScriptではそれなりのコーディングが必要になります。 なぜかというと、 PHPの場合はサーバー側で処理を行い、 ブラウザーが受け取るHTMLは既にインクルードされるべき内容が反映されたものになっていますが、JavaScriptで行う場合はまずはJavaScriptを含んだHTMLをブラウザ側で読み込み、次にJavaScriptの指示に従って、インクルードするべき内容をサーバーに取りに行き、これをHTML内部に埋め込むという作業を行った後で、整形されたHTMLをブラウザーが再度表示する、というような作業を行うからです。 JavaScriptでやるのはスマートではないなあ、と思い、PHPに興味が沸きました。 

一昔前のWebの教科書を見ると、Server Side Scripting はPerlという言語を使っています。 PHPというのはここ10年ほどで急速に認められてきた言語体系のようです。

さらに調べてみると、 WordPress ,Drupal, Joomla!など今話題のWebコンテントのマネージメントツールや、facebook,WikipediaなどのWeb serviceは皆PHPを使って作成されているようです。  これはかじってみるしかありますまい。 と思うに至り PHPの勉強をすることにした次第。