Backup сервера на Яндекс.Диск

2 февраля 2015

Как известно, админы делятся на тех кто не делает бэкапы, и тех кто уже делает.

Кнопка backup

Так то у меня есть бекапы средствами хостинга, но для собственного спокойствия решил таки сделать небольшой скриптик для бекапа всего важного на яндекс.диск, благо появился консольный клиент яндекс.диска под linux.

Особенности:
1. каждая БД сохраняется отдельным файлом, потом все вместе архивируются
2. архивируется каталог /etc
3. архивируется каталог /root
4. архивируется каталог /home
5. итоговый архив шифруется с помощью gpg
6. если архивов в папке больше 7шт (настраивается переменной MAX_FILES), то старые удаляются.

Вся установка/настройка скрипта сводится к:
1. установка и настройка клиента яндекс.диска по ссылке выше
2. копирование скрипта куда-нибудь на сервер
3. настроить логин/пароль к mysql базе

MYSQL_USER="user"
MYSQL_PASSWORD="password"

4. ввести пароль для шифрования файле бекапа (через gpg)

GPG_PASSOWRD="gpg_password"

5. прописать скрипт в cron на запуск например в 4 утра.

0       4       *       *       *       /home/cron/backup.sh  >/dev/null 2>&1

сам скрипт:

#!/bin/bash
MYSQL_USER="user"
MYSQL_PASSWORD="password"
GPG_PASSOWRD="gpg_password"
BACKUP_DIRECTORY="/home/backups"
MAX_FILES=7

databases=`mysql --user=$MYSQL_USER --password=$MYSQL_PASSWORD -e "SHOW DATABASES;" | tr -d "| " | grep -v Database`

curr_date=`date +%Y\.%m\.%d`

for db in $databases; do
    echo `date +%H:%M:%S` ": Dumping database: $db"
    OPT=""
    if [[ "$db" == "mysql" ]] ; then
        OPT=" --events"
    fi
    mysqldump --user=$MYSQL_USER --password=$MYSQL_PASSWORD --databases $db --add-drop-table --add-locks --create-options --single-transaction -Q -c -e $OPT > /tmp/$curr_date.$db.sql
done

echo `date +%H:%M:%S` ": Create DB archive"
cd /tmp
tar czf /home/db_$curr_date.tgz *.sql

echo `date +%H:%M:%S` ": Remove tmp sql files" 
rm /tmp/*.sql 

echo `date +%H:%M:%S` ": Backup etc files"
cd /etc/
tar czf /home/etc_$curr_date.tgz *

echo `date +%H:%M:%S` ": Backup root files"
cd /root/
tar czf /home/root_$curr_date.tgz *

echo `date +%H:%M:%S` ": Backup all files"
cd /home/
tar czf $BACKUP_DIRECTORY/backup_$curr_date.tgz * --exclude="backups" --exclude="*/wp-content/cache/*"

/usr/bin/gpg -c --force-mdc --batch --yes --passphrase=$GPG_PASSOWRD $BACKUP_DIRECTORY/backup_$curr_date.tgz

echo `date +%H:%M:%S` ": Remove tmp files"
rm /home/db_$curr_date.tgz  /home/etc_$curr_date.tgz /home/root_$curr_date.tgz $BACKUP_DIRECTORY/backup_$curr_date.tgz

echo `date +%H:%M:%S` ": Remove old backup files"

bf=`ls -1 $BACKUP_DIRECTORY/backup*.tgz.gpg | sort -d`
fc=`ls -1 $BACKUP_DIRECTORY/backup*.tgz.gpg | wc -l`

if [ "$fc" -gt "$MAX_FILES" ] ; then
    for file in $bf
    do
        if [ "$fc" -gt "$MAX_FILES" ] ;then
            fc=$(($fc - 1))
            rm -f $file
        fi
    done
fi

echo `date +%H:%M:%S` ": Yandex.Disk sync"
/usr/bin/yandex-disk sync

В планах:
1. сделать сохранение месячных и недельных бекапов
2. подумать над инкрементными ежедневными бекапами (хотя тут наверное проще какую-нибудь Bacula поставить) %)

Опубликовал:

Python: RSS to Twitter (oauth версия)

11 марта 2012

Старый вариант репостинга в твиттер из RSS давно не работает.

Снова понадобилась такая штука, сделал новый вариант репостинга через oauth.

Используются модули oauth-python-twitter2 и python-bitly.

Все предельно просто, настройка заключается в заполнении значений переменных


bit_api=""
bit_login=""
consumer_key=""
consumer_secret=""
access_key=""
access_secret=""
rss_url="/rss"

После чего запуск скрипта в cron в соотвествии с требованиями по скорости постинга. Можно в принципе и в CMS встроить, чтобы постинг мгновенный был.

Сам код:


#!/usr/local/bin/python
# -*- coding: utf8 -*-
import urllib, libxml2, pickle, os
from time import sleep
from oauthtwitter import OAuthApi
import bitly
bit_api=""
bit_login=""
consumer_key=""
consumer_secret=""
access_key=""
access_secret=""
rss_url="/rss"
if __name__ == '__main__':
    doc = libxml2.parseFile(rss_url)
    links=[]
    for item in doc.xpathEval('//channel/item/link'):
        links.append(item.content)
    titles=[]
    for item in doc.xpathEval('//channel/item/title'):
        titles.append(item.content)
    rss_path = os.path.join(os.path.dirname(__file__), 'rss.db')
    file = open (rss_path)
    old_links=pickle.load(file)
    file.close()
    items=[]
    f= 0
    for x in xrange( 0,len(links)):
        for old in old_links:
            f= 0
            if (old==links[x]):
                f=1
                break
        if (f== 0):
            items.append(x)
    twitter = OAuthApi(consumer_key, consumer_secret, access_key, access_secret)
    bitly_api = bitly.Api(bit_login, bit_api)
    for x in items:
        title=titles[x].decode("utf8")
        if len(title)<=120:
            print "link %s: %s" % (x,links[x])
            if x!= 0:
                sleep(15)
            try:
                twitter.UpdateStatus(title+" "+bitly_api.shorten(links[x]))
                #print title.encode("utf8")+" "+bitly_api.shorten(links[x])
            except ValueError:
                pass
    file=open(rss_path,"w")
    ser=pickle.dump(links,file)
    file.close()

Критика, как обычно, приветствуется.

Опубликовал:

Перевод текстов посредством Bing API (устарело)

23 июня 2011

Сегодня, в связи с тем что гугл собирается закрывать свой translate api, таки собрался и сотворил класс для перевода текста через SOAP сервис Bing

В связи с тем, что по каким-то странным обстоятельствам нормальной документации нет, а есть только куцый пример на VB (о ужас!) и C# с использованием видимо готовых библиотек пришлось повозиться и подбирать параметры и вид, в котором надо данные отправить сервису, чтоб он не ругался непонятными словами.

В результате родилось вот что:

<?php
class Bing_Translate_API {
 
    function translate($text, $from = 'en', $to = 'ru') {
 
        if (mb_strlen($text)>2000)
        {
            echo "шмахтунг\n";
            return false;
        }
 
        $c=new SoapClient("http://api.bing.net/search.wsdl",array(
            "AppId"=>"bingApiId",
            "Version"=>"2.2"
        ));
 
        $arr=array(
            "parameters"=>array(
                "AppId"=>"bingApiId",
                "Market"=>"en-us",
                "Version"=>"2.2",
                "Sources"=>array(
                    "Translation"
                ),
                "Translation"=>array(
                    "SourceLanguage"=>$from,
                    "TargetLanguage"=>$to
                ),
                "Query"=>$text)
        );
 
        $r=$c->Search($arr);
 
        if (isset($r->parameters->Translation->Results->TranslationResult->TranslatedTerm))
        {
            return $r->parameters->Translation->Results->TranslationResult->TranslatedTerm;
        }
        else
        {
            echo "ахтунг\n";
            return false;
        }
 
 
    }
}
?>

а вызывать пользоваться этим безобразием теперь вообщем-то просто:

<?php
echo Bing_Translate_API::translate("text to translate","en","ru");
?>

Кроме того, выяснилось, что у Bing API есть ограничение на размер запроса примерно в 2кБ текста. А Примерно потому, что через раз он текст с размером 2048 символов не переводил, уж не знаю почему.

BingApiId получать по ссылке http://www.bing.com/developers/

З.Ы. я знаю что код кривой, и при малейшем чихе упадет, но делать ничего не буду, это есть пример 🙂

Опубликовал:

Изобретая велосипеды: xmlrpc

19 апреля 2011

Вчера озаботился возможностью что-то опубликовать в блоке посредством xmlrpc из php.

Проковырявшись в общей сложности часа 4 с библиотекой xmlrpc и встроенной функцией xmlrpc_encode_equest и так и не добившись нормальной работы всего этого безобразия, собрался и сегодня за 1.5 часа сделал свой велосипед 🙂

<?php
/*
 * Велосипед для работы с xmlrpc
 * версия 0.1beta
 *
 * Copyright (c) 2011 mrdaark.
 * Все права защищены
 * 
 */
 
/*
 * Функция для отправки запроса на xmlrpc сервер
 * использует библиотеку curl
 * в качестве параметров:
 * @url - url адрес сервера
 * @request - запрос формируемый с помощью функции xmlrpc_request
 */
function xmlrpc_send($url,$request)
{
    if(!function_exists('curl_init')) {
        die ("Curl PHP package not installed\n");
    }
 
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml; charset=utf-8"));
    curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
    $response = curl_exec($ch);
    $response = xmlrpc_decode($response);
    curl_close($ch);
    return $response;
}
 
/*
 * Функция формирования xml запроса на xmlrpc
 * в качестве параметров:
 * @method - вызываемый метод
 * @params - массив списка параметров
 */
function xmlrpc_request($method,$params)
{
    $xml ="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
    $xml.="<methodCall><methodName>".$method."</methodName><params>";
 
    if (is_array($params))
    {
        foreach ($params as $v)
        {
            if (is_numeric($v))
            {
                $xml.="<param><value><int>".$v."</int></value></param>";
            }
            else if (is_string ($v))
            {
                $xml.="<param><value><string>".$v."</string></value></param>";
            }
            else if (is_bool($v))
            {
                $xml.="<param><value><boolean>". ($v ? "1":"0") ."</boolean></value></param>";
            }
            else if (is_array($v))
            {
                $xml.="<param>"._xmlrpc_genstruct($v)."</param>";
            }
        }
    }
    $xml.="</params></methodCall>";
    return $xml;
}
 
/*
 * Функция формирования именованной структуры
 * в качестве параметров:
 * @params - массив списка параметров
 */
function _xmlrpc_genstruct($params)
{
    if (is_array($params))
    {
        $xml="<value><struct>";
        foreach ($params as $k=>$v)
        {
            //формируем поля запроса.
            if (is_numeric($v))
            {
                $xml.="<member><name>".$k."</name><value><int>".$v."</int></value></member>";
            }
            else if (is_string ($v))
            {
                $xml.="<member><name>".$k."</name><value><string>".$v."</string></value></member>";
            }
            else if (is_bool($v))
            {
                $xml.="<member><name>".$k."</name><value><boolean>". ($v ? "1":"0") ."</boolean></value></member>";
            }
            else if (is_array($v))
            {
                $xml.="<member><name>".$k."</name>";
                if (_xmlrpc_is_struct($v))
                {
                    $xml.=_xmlrpc_genstruct($v);
                }
                else
                {
                    $xml.=_xmlrpc_genarray($v);
                }
                $xml.="</member>";
            }
        }
        $xml.="</struct></value>";
        return $xml;
    }
    else
    {
        return "";
    }
}
 
/*
 * Функция формирования неименованного массива
 * в качестве параметров:
 * @params - массив списка параметров
 */
function _xmlrpc_genarray($params)
{
    if (is_array($params))
    {
        //проверим необходимость добавления имени
        $xml="<value><array><data>";
        foreach ($params as $v)
        {
            //формируем поля запроса.
            if (is_numeric($v))
            {
                $xml.="<value><int>".$v."</int></value>";
            }
            else if (is_string ($v))
            {
                $xml.="<value><string>".$v."</string></value>";
            }
            else if (is_bool($v))
            {
                $xml.="<value><boolean>". ($v ? "1":"0") ."</boolean></value>";
            }
            else if (is_array($v))
            {
                if (_xmlrpc_is_struct($v))
                {
                    $xml.=_xmlrpc_genstruct($v);
                }
                else
                {
                    $xml.=_xmlrpc_genarray($v);
                }
            }
        }
        $xml.="</data></array></value>";
        return $xml;
    }
    return "";
}
 
/*
 * Функция проверки переданного массива на
 * предмет необходимости формирования
 * именованной структуры
 * в качестве параметров:
 * @params - массив списка параметров
 */
function _xmlrpc_is_struct($params)
{
    $keys=array_keys($params);
    foreach ($keys as $k=>$v)
    {
        if ($k!=$v)
        {
            return true;
        }
    }
    return false;
}
?>

Пользоваться безобразием просто, вот примерчик постинга в блог на wordpress:

<<?php
require("xmlrpc.php");
//формируем массив с параметрами запроса
$content=array();
$content['title']="Тестовая запись";
$content['categories'] = array("news");
$content['description']="Описание тестовой запись (то, что до тега <!--more-->)";
$content['mt_text_more']="Собственно основная часть постинга";
$content['mt_keywords']="ключевые, слова";
 
//формируем xml запрос
$request=xmlrpc_request("metaWeblog.newPost",
        array(1,"admin","password",$content,false));
 
//отправляем серверу
xmlrpc_send("http://example.com/wordpress/xmlrpc.php",$request);
?>

вроде все просто и понятно. Работает на данный момент только с кодировкой UTF-8. Собственно и затеялось как раз потому, что два предыдущих варианта напрочь отказывались нормально отправлять в UTF-8.

Реализованы не все типы, возможные в xmlrpc, однако мне для постинга в WordPress этого пока хватит, буде кому понадобится еще чего можете меня попинать — допишу, ну или сами, а я добавлю со ссылкой на автора 🙂

P.S. походу wordpress чудит с кавычками. файл исходника xmlrpc.phps

Опубликовал:

Решение задачки для php программистов с хабра

5 апреля 2011

Увидел на хабре задачку при собеседовании для php программистов http://habrahabr.ru/blogs/php/116686/

Решил попробовать сам ее решить, вроде не сложно. вместе с отладкой, сделал за ~20минут, т.е. в норматив не уложился, что херово.

Получилось вот так,  хз насколько это оптимально. Есть идеи что улучшить?

<?php
function read_conf($path)
{
    $f=file_get_contents($path);
    //разбиваем на строки
    $lines=explode("\n",$f);
    $tmp=array();
 
    //режем строки на key=>value
    foreach($lines as $v)
    {
        if (trim($v)!="")
        {
            list($key,$value)=explode("=",$v,2);
            $tmp[trim($key)]=trim($value);
        }
    }
 
    $res=array();
    //режем ключи по параметрам
    foreach ($tmp as $key=>$value)
    {
        $keys=explode(".",$key);
        if (count($keys)!=1)
        {
            //тут будем делать подмассивчики
            $key=array_pop($keys);
            $first_key=array_shift($keys);
            if (!isset($res[$first_key]))
            {
                $res[$first_key]=array();
            }
            $pres=&$res[$first_key];
            while (count($keys)!= 0)
            {
                $tmp_key=array_shift($keys);
                if (!isset($pres[$tmp_key]))
                {
                    $pres[$tmp_key]=array();
                }
                $pres=&$pres[$tmp_key];
            }
            $pres[$key]=$value;
        }
        else
        {
            $res[$key]=$value;
        }
    }
    return $res;
}
print_r(read_conf("conf.ini"));
?>

Опубликовал:

Python: Простой пример использования twisted

Надо мне написать небольшой сервер, который будет принимать некие данные, сохранять их, а потом по определеным командам отдавать. Сейчас есть нечто такое же, только реализованное на LAMP, работает прямо скажем не очень быстро. Можно конечно сделать какое-то кеширование, memcache и т.д., но мне не хватает свободы маневра в текущей структуре, поэтому я решил таки переписать серверную часть, ибо клиентская вполне себе меня удовлетворяет.

Вообще решил сделать отдельный сервис, висящий на отличном от 80 порту и выполняющий все что мне надо. Писать решил с использование сетевого фреймворка twisted. Штука прикольная, но вот незадача, нормальной документации и примеров как то нету, того что на их сайте есть, мне явно не хватает, для того чтобы понять как и что нужно делать. Поэтому решил для начала написать простенький примерчик, который будет висеть и слушать запросы и в зависимости от url увеличивать или уменьшать внутренний счетчик.

Вот что получилось:

__author__="mrdaark"
__date__ ="$15.07.2009 21:31:01$"
 
from twisted.web import server, resource
from twisted.internet import reactor
import string
 
class Consult():
    count= 0
        def add(self,x=1):
            self.__class__.count+=x
        def remove(self,x=1):
            self.__class__.count-=x
 
class Server(resource.Resource):
    isLeaf = True
        def getChild(self, name, request):
            if name == '':
                return self
            return Resource.getChild(self, name, request)
        def render(self, request):
            urlpath=string.split(request.path[1:],'/')
            path=urlpath[ 0]
            if len(urlpath)>1:
                r=int(urlpath[1])
            else:
                r=1
 
            str='<html><head></head><body>'
            c=Consult()
            if (path=='add'):
                c.add(r)
            elif (path=='remove'):
                c.remove(r)
            str+= "x: %d<br />" % (c.count)
            str+='<a href="/add/1">add 1</a><br /><a href="/remove/1">remove 1</a></body></html>'
            return str
 
def main():
    site = server.Site(Server())
    reactor.listenTCP(8080, site)
    reactor.run()
 
if __name__ == "__main__":
    main()
 

Штука запускается, вешается на 8080 порт и принимает http запросы. Увеличивает и уменьшает внутренний счетчик в зависимости о второго параметра в урл, т.е. формат урл таков: /<функция>/<параметр>. По-умолчанию, параметр равен единице.

Вот такая штука вообщем получилась. Теперь буду усложнять этот примерчик, чтобы он сохранял и выводил список элементов, так и родится нужный мне функционал. 🙂

Опубликовал:

Python: RSS to Twitter

29 июня 2009

С недели 2 назад для одного хитрого сайта понадобилось сделать автоматический репост статей в твиттер. Решил не писать на php, а попробовать на python’е наваять. Откровенного говоря очень понравилось! Получилось очень просто и легко.

Написал наверное за пол часа, что с учетом того, что python я вообщем то не знаю совсем не плохо.

  1. #!/usr/local/bin/python
  2. # -*- coding: utf8 -*-
  3.  
  4. import urllib
  5. import libxml2
  6. import pickle
  7. import twitter
  8. import os
  9. from time import sleep
  10.  
  11. bit_api="bit.ly.api"
  12. bit_login="bit.ly.login"
  13. twit_login="name"
  14. twit_pass="pass"
  15. rss_url="http://feed"
  16.  
  17.  
  18. def shorten_url(long_url, login_user, api_key):
  19.     try:
  20.         longUrl = urllib.urlencode(dict(longUrl=long_url))
  21.         login = urllib.urlencode(dict(login=login_user))
  22.         apiKey = urllib.urlencode(dict(apiKey=api_key))
  23.  
  24.         encodedurl="http://api.bit.ly/shorten?version=2.0.1&%s&%s&%s" % (longUrl, login, apiKey)
  25.  
  26.         request = urllib.urlopen(encodedurl)
  27.         responde = request.read()
  28.         request.close()
  29.         responde_dict = eval(responde)
  30.         short_url = responde_dict["results"][long_url]["shortUrl"]
  31.         return short_url
  32.     except IOError, e:
  33.         raise "urllib error "
  34.  
  35.  
  36. if __name__ == '__main__':
  37.     doc = libxml2.parseFile(rss_url)
  38.     links=[]
  39.     for item in doc.xpathEval('//channel/item/link'):
  40.         links.append(item.content)
  41.  
  42.     titles=[]
  43.     for item in doc.xpathEval('//channel/item/title'):
  44.         titles.append(item.content)
  45.  
  46.     rss_path = os.path.join(os.path.dirname(__file__), 'rss.db')
  47.     file = open (rss_path)
  48.     old_links=pickle.load(file)
  49.     file.close()
  50.  
  51.     items=[]
  52.     f=0
  53.     for x in xrange(0,len(links)):
  54.         for old in old_links:
  55.             f=0
  56.             if (old==links[x]):
  57.                 f=1
  58.                 break
  59.         if (f==0):
  60.             items.append(x)
  61.  
  62.     api = twitter.Api(username=twit_login, password=twit_pass)
  63.  
  64.     for x in items:
  65.         title=titles[x].decode("utf8")
  66.         if len(title)<=120:
  67.             print "link %s: %s" % (x,links[x])
  68.             if x!=0:
  69.                 sleep(15)
  70.             api.PostUpdate(title+" "+shorten_url(links[x], bit_login, bit_api))
  71.  
  72.     file=open(rss_path,"w")
  73.     ser=pickle.dump(links,file)
  74.     file.close()
  75.  

Какие трудности вылезли в процессе:

1. Нужно как-то было не постить то что уже отправлено. Решилось сохранением списка ссылок в сериализованном виде в файл

2. Размер сообщение в твиттере не больше 140 символов, а надо бы в твитты еще и ссылки на статьи вставлять, притом что заголовки у статей я бы сказал не маленькие. Решилось регистрацией на bit.ly (сервис укорачивания ссылок) и собственно укорачивание. Не помню уже сам написал или с сайта их взял функцию, ибо там все так просто что даже неинтересно.

3. Невероятно, но встала проблема с кодировкой. Решилось за 5 минут гугления и пробования нескольких комбинаций. Что-то не понимал я откуда куда функции перекодируют. Вобщем вот так надо

title.decode(«utf8»)

4. Ну и задержку пришлось поставить, иначе bit.ly говорило зайдите позже 🙂

Дальше все это безобразие запихал в крон:

*/12 * * * * /usr/local/bin/python /usr/local/www/rss2twitter/rss2twit.py

За время пока это дело работает, уже сделан репост 1256 твиттов, так что красота.

А вообще все очень просто и легко, особенно учитывая что twitter api уже написано. хотя если бы его небыло, думаю я бы просто логинился, сохранял куки и постил обычным POST запросом.

Ну и файлик со скриптом на последок: rss2twit.tar.gz

З.Ы. с удовольствием выслушаю критику, потому как первое не Hello, world! приложение на python’е. 🙂

Опубликовал:

MySQL: базу знаю :)

Сегодня, не то чтобы от нечего делать, а так, прошел курс на сайте http://www.intuit.ru/ «Введение в СУБД MySQL». Прошел так, чтобы себя проверить, а то совсем самоучка, а тут хоть какое-то структурирование знаний. Честно скажу практически ничего нового для себя не вынес, пройти было на удивление легко, с первым тестом повозился (там теоретические знания по СУБД), никак он мне отлично ставить не хотел, все хорошо да хорошо. :))

Но вообщем все остальные, направленные на практику, сдал без проблем и сразу на отлично, вообщем-то как и экзамен. Местами правда у них в вопросах неоднозначные ответы, зависящие от нераскрытых входных данных, но это фигня 🙂

Cертификат даже себе выписал (правда только электронный). Посмотреть можно по адресу: http://www.intuit.ru/diploma/lQ2wte8s3eAw/P00057527/. Вообщем штука прикольная, надо будет поискать еще курсов, может чего дельного для себя найду, заодно и экспиренс прокачаю 🙂

Опубликовал:

Изобретая велосипеды: PHP шаблонизатор

13 февраля 2009

Началось все с того что мне не нравятся существующие темплейтеры типа smarty. Не прет.

Написал свой, простенький, но для повседневности хватает.

вот такой вот класс:

  1. <?php
  2. class Template {
  3.     private $template;
  4.     private $vars;
  5.     private $blocks;
  6.     private $res;
  7.     private $registry;
  8.  
  9.     function __construct($registry) {
  10.         $this->registry = $registry;
  11.     }
  12.  
  13.     private function getvars($block="")
  14.     {
  15.         $str="";
  16.         if ($block=="")
  17.         {
  18.             $str=$this->res;
  19.         }
  20.         else
  21.         {
  22.             if (isset($this->blocks[$block]))
  23.             {
  24.                 $str=$this->blocks[$block];
  25.             }
  26.         }
  27.         $m=array();
  28.         preg_match_all("/{%(.*)?%}/ismU",$str,$m,PREG_SET_ORDER);
  29.         return $this->assoc($m);
  30.     }
  31.  
  32.     private function assoc($arr)
  33.     {
  34.         $r=array();
  35.         if (count($arr)>0)
  36.         {
  37.             foreach ($arr as $v)
  38.             {
  39.                 $r[$v[1]]=$v[0];
  40.             }
  41.         }
  42.         return $r;
  43.     }
  44.  
  45.     private function getblocks()
  46.     {
  47.         $m=array();
  48.         preg_match_all("/<!--(.*)?-->.*?<!--\/\\1-->/ismU", $this->template, $m ,PREG_SET_ORDER);
  49.         $this->blocks=$this->assoc($m);
  50.     }
  51.  
  52.     private function getinclude($data)
  53.     {
  54.         $m=array();
  55.         preg_match_all("/<!--include:(.*)?-->/ismU", $this->template, $m ,PREG_SET_ORDER);
  56.         foreach ($m as $i)
  57.         {
  58.             $t=new template($this->registry);
  59.             $this->res=str_replace($i[0],$t->compile($data,site_path.$i[1]),$this->res);
  60.             unset($t);
  61.         }
  62.     }
  63.  
  64.     function getphp()
  65.     {
  66.         $m=array();
  67.         preg_match_all("/{--(.*)?--}/ismU", $this->res, $m ,PREG_SET_ORDER);
  68.         $out="";
  69.         foreach ($m as $p)
  70.         {
  71.             ob_start();
  72.             eval ("echo ".$p[1].";");
  73.             $out = ob_get_clean();
  74.             $this->res=str_replace($p[0],$out,$this->res);
  75.         }
  76.     }
  77.  
  78.     private function setvars($tpl,$vars,$d)
  79.     {
  80.         $str=$tpl;
  81.         foreach ($vars as $var => $search)
  82.         {
  83.             if (isset($d[$var]))
  84.             {
  85.                 $str=str_replace($search,$d[$var],$str);
  86.             }
  87.             else
  88.             {
  89.                 $str=str_replace($search,"",$str);
  90.             }
  91.         }
  92.         return $str;
  93.     }
  94.  
  95.     private function compileblock($block,$data)
  96.     {
  97.         $tpl=$this->blocks[$block];
  98.         $res="";
  99.         $vars = $this->getvars($block);
  100.         //print_r($vars);
  101.         foreach($data as $d)
  102.         {
  103.             $res.=$this->setvars($tpl,$vars,$d)."\n\n";
  104.         }
  105.         $this->res=str_replace($tpl,$res,$this->res);
  106.     }
  107.  
  108.     function compile($data,$tplfile)
  109.     {
  110.         $this->template=file_get_contents($tplfile);
  111.         $this->res=$this->template;
  112.         $this->getblocks();
  113.         foreach ($this->blocks as $block => &$v)
  114.         {
  115.             if (isset($data[$block]))
  116.             {
  117.                 $this->compileblock($block,$data[$block]);
  118.             }
  119.             else
  120.             {
  121.                 $this->res=str_replace($this->blocks[$block],"",$this->res);
  122.             }
  123.         }
  124.         $this->res=$this->setvars($this->res,$this->getvars(),$data);
  125.         $this->getinclude($data);
  126.         $this->getphp();
  127.  
  128.         return $this->res;
  129.     }
  130.  
  131.     function html($data,$tplfile)
  132.     {
  133.         echo $this->compile($data,site_path.$tplfile);
  134.     }
  135. }
  136. ?>

Чтобы было проще понять, вот пример шаблона:

  1. <!--include:tpl/header.tpl-->
  2. <table class="adminlist" style="width:300px;">
  3. <tr>
  4. <th class="title" width="3%"><input name="toggle" id="toggle" value="1" onclick="checkAll(1);" type="checkbox" /></th>
  5. <th class="title" width="97%">&nbsp;</th>
  6. </tr>
  7.  
  8. <!--users-->
  9. {--('1'=='{%flag%}') ? '<tr><td colspan="2">{%gname%}</td></tr>':'' --}
  10. <tr class="row0">
  11. <td><input type="checkbox" class="checkbox" name="check_{%id%}" /></td>
  12. <td><a href="/users/edit/{%id%}">{%name%}</a></td></tr>
  13. <!--/users-->
  14.  
  15. {--('write'=='{%right%}') ? '<tr><td colspan="6" style="text-align:center;"><a href="/users/create">Добавить</a></td></tr>':''--}
  16. </table>
  17.  
  18. <!--include:tpl/footer.tpl-->

Как использовать? примерно вот так:

  1. <?php
  2. $d=array();
  3. $d['users'][1]['id']=1;
  4. $d['users'][1]['name']='User 1';
  5. $d['users'][1]['game']='Group 1';
  6. $d['users'][1]['flag']=1;
  7. $d['users'][2]['id']=2;
  8. $d['users'][2]['name']='User 2';
  9. $d['users'][2]['game']='Group 1';
  10. $d['users'][2]['flag']=0;
  11. $d['users'][3]['id']=3;
  12. $d['users'][3]['name']='User 3';
  13. $d['users'][3]['game']='Group 2';
  14. $d['users'][3]['flag']=1;
  15. $d['right']='right';
  16.  
  17. $template = new Template($registry);
  18. $template->html($d,'tpl/usersform.tpl');
  19.  
  20. ?>

Поподробнее про теги шаблона:
{%name%} — переменная
<!—include:file.tpl—> — включение внешнего шаблона (количество включений ограничено только оперативной памятью 🙂
повторяющиеся блоки оформляются так
<!—blockname—>
{%name%}
<!—/blockname—>
где blockname — это массив значений в передаваемом параметре
условия задаются вот так:
{—(‘1’=='{%flag%}’) ? ‘{%var1%}’:'{%var2%}’ —}

ссылочка на файлик класса: template.phps

Опубликовал:

Изобретая велосипеды: AJAX

2 декабря 2008

короче, суть в том, что нужно мне было сделать штуку, чтобы удобно было делать запросы через ajax и не менее удобно обрабатывать ответы от них.

так вот, если раньше я таскал за собой или дикий кусок js, изменять который было сложно или некий фреймворк типа jQuery, то теперь я сделал простенький объект, который делает всю грязную работ,. а мне остается только передать параментры и функцию обработки, и поскольку я не жадный, то нате:

if (typeof jsAjax == "undefined" || !jsAjax) {
  var jsAjax = {};
}

jsAjax.ajax = typeof jsAjax.ajax != 'undefined' && jsAjax.ajax ? jsAjax.ajax : function(o) {
  var url = (typeof o === 'string') ? o : o.url;
  var callback = (typeof o.callback === 'undefined') ? function() {} : o.callback;
  var method = (typeof o.method === 'undefined') ? 'GET' : o.method;
  var type = (typeof o.type === 'undefined') ? 'text' : o.type;

  var len = (typeof o.vars === 'undefined') ? 0 : o.vars.length;
  var str="";
  for(var i = 0;i < len;i++)
  {
    if(i!='0')
    {
      str=str+'&';
    }
    str=str+o.vars[i].name+'='+escape(o.vars[i].value);
  }

  var xhr = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();

  var onCompleteRequest = function () {
    if (xhr.readyState == 4)
    {
      if (xhr.status == 200)
      {

        if (type == "text") {
          r=xhr.responseText;
        } else if (type=="json") {
           r=eval('('+xhr.responseText+')');
        } else if (type=="xml") {
          r=xhr.responseXML.documentElement;
        } else {
          r=xhr.responseText;
        }
        callback(r);
      }
    }
  };

  xhr.open(method, url, true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.setRequestHeader('Accept-Language', 'en');
  xhr.setRequestHeader('Accept-Charset', 'utf-8');
  xhr.onreadystatechange=onCompleteRequest;
  xhr.send(str);

};

Пользоваться безумно просто, чтоб было более менее понятно, несколько примеров:

jsAjax.ajax("http://example.com/");

jsAjax.ajax({url:"http://example.com/",vars:[{name:"x",value:"y"},{name:"y",value:"x"}],method:"POST",callback:function() {alert(r);}});

jsAjax.ajax({url:"http://example.com/",vars:[{name:"x",value:"y"},{name:"y",value:"x"}],method:"POST",type:"json",callback:someFunction});

теперь по поводу параметров:

можно передать просто текст, тогда он будет считаться адресом и сделает тупой GET запрос, а можно поступить умнее и заполнить структурку состоящюю из следующих полей

url — адрес который запрашиваем
vars — список параметров запроса в виде:

[{name:"someName",value:"someValue"},{name:"someOtherName",value:"someOtherValue"}]

method — метод запроса (GET или POST)
type — тип ответа передаваемый в функцию обработки (text,json,xml)
callback — функция обработки ответа (ответ в соответствии с типом как видно из кода передается одной переменной)

вообщем вызов с полным списком переменных будет выглядеть примерно так:

jsAjax.ajax({
url:"http://somedome.com/somepage.php",
vars:[{name:"someName",value:"someValue"},{name:"someOtherName",value:"someOtherValue"}],
method: "POST",
type: "text",
callback: function (answer) { alert(answer); }
});

вот вообщем и все. 🙂
P.S. вот ссылочка на файл исходника.

Опубликовал: