Показаны сообщения с ярлыком php. Показать все сообщения
Показаны сообщения с ярлыком php. Показать все сообщения

среда, 4 февраля 2015 г.

Домашняя бухгалтерия на PHP 5, SQLite 3, ExtJs 4

Домашняя бухгалтерия на ExtJS 4, PHP 5, SQLite 3.
Демо - http://demobuh.skahin.ru
Пароль и логин: admin

Описание возможностей и онлайн использование: http://buh.skahin.ru

Разработка и исходный код: https://github.com/pihel/cash

четверг, 19 июня 2014 г.

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

Почти все, кто разрабатывает на bitrix, знает, что можно создавать стили для визуального редактора.
Но тут возникает проблема, если стили визуального редактора отличаются от стилей сайта, а контент вставляется в разные части страницы, то нужно как-то однозначно определить место вставки текста из редактора.

Для решения этой проблемы я решил обернуть весь текст из визуального редактора в div с определенным классом и все стили наследовать от него.
Т.е. в файле стилей нам можно будет прописать оформление для всех элементов наследников этого класса. Тогда оформление будет выглядеть одинаково как в самом визуальном редакторе, так и в любой части страницы, т.к. весь код будет обернут в этот div.
Остается только вставлять этот див автоматически в визуальный редактор.
Для этого в init.php добавляем обработчик события подключения визуального редактора и при его инициализации создаем div в который поместим весь контент.
<?php
//событие подключения визуального редактора
AddEventHandler("fileman", "OnIncludeHTMLEditorScript", array("MyHandlers", "OnIncludeHTMLEditorScript"));


class MyHandlers {

   public static function OnIncludeHTMLEditorScript()
   {
      ?>
      <script type="text/javascript">
      //получаем указатель на полотно визуального редактора
      BXHTMLEditor.prototype.SetView_ = BXHTMLEditor.prototype.SetView;
      BXHTMLEditor.prototype.SetView = function (sType) {
         var result = this.SetView_(sType);
         var html = undefined;
         if(BX.browser.IsIE()) {
            try {
               html = this.pEditorDocument.body;
            } catch (e) {
               //
            }         
         } else {
            try {
               html = this.pEditorWindow.document.body;
            } catch (e) {
              //
            }
         }
         if( sType == "html" && html ) {
            //обертываем в DIV, если его еще нет
            if( html.innerHTML.indexOf('<div class="content">') == -1 ) {
              html.innerHTML = '<div class="content">'+html.innerHTML+'</div>';
            }
         }
         return result;
      };
      </script>
      <?
   }
}
?>

понедельник, 28 апреля 2014 г.

Две заметки о Bitrix

1. Для создания сложных форм на bitrix я использую кастомный компонент в котором вытаскиваю все данные о инфоблоке с помощью такого запроса:
function getIblockProps($id) {
 global $DB;
 $sql = "SELECT b.id, b.code, b.name,
   -----
   p.id as pid, p.code as pcode, p.name as pname, p.SORT psort, 
    UPPER(CASE 
     WHEN p.USER_TYPE IS NOT NULL THEN p.USER_TYPE
     WHEN p.PROPERTY_TYPE = 'S' AND p.ROW_COUNT > 1 THEN 'T'
     ELSE p.PROPERTY_TYPE
    END) as ptype, 
    CASE WHEN p.MULTIPLE = 'Y' THEN 1 END as pmultiple, CASE WHEN p.MULTIPLE = 'Y' THEN p.MULTIPLE_CNT END pmultiple_cnt, 
   CASE WHEN p.LINK_IBLOCK_ID > 0 THEN p.LINK_IBLOCK_ID END as piblock, 
   CASE WHEN p.IS_REQUIRED = 'Y' THEN p.IS_REQUIRED END preq, p.HINT as phint,
   -----
   e.ID as eid, e.VALUE as evalue, e.DEF as edef, e.SORT as esort
  FROM b_iblock b , b_iblock_property p
  LEFT JOIN b_iblock_property_enum e
  ON(e.property_id = p.id)
  WHERE b.id = %u
  AND b.id = p.IBLOCK_ID AND p.ACTIVE = 'Y'
  ORDER BY p.sort,e.SORT";
 $rs = $DB->Query( sprintf( $sql, intval($id) ) );

 $rows = array();
 while ($row = $rs->Fetch()) {
  $rows[] = $row;
 }

 return $rows;
}

Запрос берет информацию о полях и свойствах инфоблока. Если поле ссылочное, то функцию можно вызывать рекурсивно.

2. Особенность создания товарных предложений через код.
Создали мы товарное предложение через код:
$el = new CIBlockElement;  
$id = $el->Add($arFields);

Задали цену:
CPrice::SetBasePrice( $id,  $arFields['PRICE'],  "RUB", 1);

Заполнили остатки:
$arFields = Array(
  "PRODUCT_ID" => $id,
  "STORE_ID" => 1,
  "AMOUNT" => $v['Количество'],
 );
 
CCatalogStoreProduct::Add($arFields);

Вроде все. Но товар не покупается, хотя все атрибуты присутствуют.

Чтобы все заработало, нужно еще раз добавить (именно добавить, а не обновить) предложение, но другой функцией:
$arFields = array('ID'=>$id, 'PRICE_TYPE' => 'S', 'QUANTITY' => floatval($f['Количество']));
CCatalogProduct::Add($arFields);

воскресенье, 28 июля 2013 г.

SQLite: две особенности этой базы

Хочу рассказать про 2 отличия SQLite от других БД, решение которых придется долго искать, если вы только начинаете работать с ней.

1. Приведение к верхнему регистру UPPER юникод строк UTF8.
Необходимость в этом у меня появилась при регистронезависимом поиске по подстроке LIKE и как оказалось поумолчанию эта функций в SQLite нет и предлагается самим ее реализовать.
В интернете множество вариантов решения через настройку SQLite и доустановку дополнительных пакетов. Но мне такой способ не подходил, т.к. на шаред хостинге это не сделать.

К счастью SQLite позволяет делать собственные пользовательские функции, чем мы и воспользуемся.
Далее код на php для создания собственной функции UPPER - UPPER_UTF8.
createFunction('UPPER_UTF8', 'upper_ru', 1);
//array('SQLITE_DB', 'upper_ru') - если используете в составе класса 'SQLITE_DB'

?>
После этих нехитрых манупуляций в составе SQLite появится новая функция UPPER_UTF8 и с помощью ее можно будет делать регистронезависимые запросы:
SELECT 
  t.* 
FROM
  table t
WHERE
  UPPER_UTF8(t.col) LIKE UPPER_UTF8('%кАкойТо Запрос%')

2. Правильное создание индексов для поиска по диапазону в БД SQLite.
Если вы хотите осуществить быстрый поиск по таблице, то во всех субд для этого необходимо создать индекс. Но в SQLite еще нужно соблюсти правильную последовательность.
Так если вы создаете индекс по нескольким полям, то столбец диапозона должен обязательно идти последним.

Далее 2 примера, сначала неправильный:
--таблица
CREATE TABLE "cashes" (
    "id" INTEGER PRIMARY KEY  NOT NULL,
    "date" DATE NOT NULL,
    "uid" INT(10) NOT NULL DEFAULT ('1'),
    "visible" TINYINT(4) NOT NULL DEFAULT ('1')
);

--индекс
CREATE INDEX "XIF_CASHES_DUV" on cashes (date DESC, uid ASC, visible ASC);

--вызов плана
EXPLAIN QUERY PLAN SELECT
      c.id, c.uid, c.date
     FROM cashes c
     WHERE
      c.date BETWEEN '2013-04-01' AND '2013-06-01'
      AND c.uid = 1 AND c.visible = 1
     ORDER BY
      c.date;

--план
SEARCH TABLE cashes AS c USING INDEX XIF_CASHES_USR (uid=?) (~2 rows)
А дальше, как нужно было правильно создавать индекс. Поле даты - поле диапозона, должно было идти последним:
--индекс
CREATE INDEX "XIF_CASHES_DUV" on cashes (uid ASC, visible ASC, date DESC);

--план запроса
SEARCH TABLE cashes AS c USING INDEX XIF_CASHES_DUV (uid=? AND visible=? AND date>? AND date<?) (~42 rows)
Причем если бы мы искали точное соответствие по дате, то сработали бы оба индекса, но при поиске по диапазону только последний.

воскресенье, 15 апреля 2012 г.

Демон и клиент Gearman

Сегодня пойдет речь о расширении к PHP Gearman.
Исходный код проекта: GearmanDaemon


Gearman это универсальный фреймворк разработки приложений для раздачи работ множеству машин или процессов. Это позволяет приложениям выполнять задачи параллельно, распределяя нагрузку и использовать функции различных языков. Фреймворк может использоваться во множестве приложений, от высоконадежных веб-сайтов до транспорта событий репликации базы данных.

Т.е. у нас есть задача не на 15 секунд, допустим сделать несколько ресайзнутых копии загруженных пользователем фотографии в наилучшем качестве. Если это делать синхронно, то это может занять до нескольких минут. Для устранения проблемы ожидания создано это расширение.

Принцип работы:
  1. Клиент: Получаем данные от клиента (набор фотографии или другое.)
  2. Клиент: Разбиваем данные на группы по какому то признаку
  3. Клиент: Ставим задачи серверам: каждому серверу или разных потокам сервера своя задача
  4. Сервер: Демон сервера, на любом языке, получает новое задание, стартует поток и выполняет задание
В случае асинхронного выполнения, ответ обратно не отсылается. Но если задача выполняется синхронно, просто с разбивкой по потокам или серверам, то можно отправить ответ.

Более подробное описание реализации под катом.

воскресенье, 19 июня 2011 г.

Адаптация модуля связи Punbb на Drupal 7

Api Drupal 7 сильно изменилось, в связи с этим перевод модуля был долгим, но я все же это сделал :)
Предлагаю всем желающим обновиться.

Ссылка для скачивания модуля новая: http://pihel.myjino.ru/files/punbb7.zip

воскресенье, 15 мая 2011 г.

Особенности OnBeforeIBlockElementUpdate в Bitrix

В ходе работы сайта на Bitrix вдруг начали пропадать содержания статей.
Сначала я грешил на человеческий фактор, что кто-то случайно или специально удаляет содержания статей.

После недолгих разбирательств (создания истории изменений) выяснилось, что ошибка скорей техническая. Т.к. ошибку удалось локализовать, она проявлялась при привязки раздела к статье из списка элементов. Тогда как изменение раздела из самой статьи не приводило к ошибке.
Следующим этапом было штудирование файла init.php, где хранятся обработчики событий портала.
В файле init.php хранился обработчик, заменяющий текст статьи на другой в автоматическом режиме.
AddEventHandler("iblock", "OnBeforeIBlockElementUpdate", Array("MyClass", "OnBeforeIBlockElementUpdateHandler"));

class MyClass
{
    function OnBeforeIBlockElementUpdateHandler(&$arFields)
    {
       $arFields["DETAIL_TEXT"] = str_replace('src="../../upload/', 'src="/upload/', $arFields["DETAIL_TEXT"]);
    }
}

Оказывается при привязке статьи к разделу или переносе статьи между разделами в списке элементов, событию OnBeforeIBlockElementUpdate идут не все поля и такая обработка затирает текущий детальный текст статьи. Ошибка естественно не проявлялась при редактировании элемента, т.к. в этом случае передавались все поля.

Правильный код в этом случае выглядит так (т.е. необходима проверка существования поля):
AddEventHandler("iblock", "OnBeforeIBlockElementUpdate", Array("MyClass", "OnBeforeIBlockElementUpdateHandler"));

class MyClass
{
    function OnBeforeIBlockElementUpdateHandler(&$arFields)
    {
        if(!empty($arFields["DETAIL_TEXT"])) {
          $arFields["DETAIL_TEXT"] = str_replace('src="../../upload/', 'src="/upload/', $arFields["DETAIL_TEXT"]);
        }
    }
}

среда, 29 сентября 2010 г.

Кастомизация визуального редактора Bitrix

Довольно частая вещь как вставка изображения через визуальный редактор с возможностью увеличения в Bitrix никак не решена.

Можно конечно загрузить 2 варианта изображения (большое и маленькое), в текст вставить изображение превью и указать ссылку на большое. Но хотелось бы автоматизировать этот процесс.

Официальная тех. поддержка отказалась решать эту проблему, а документация не содержала списка событий. Хорошо хоть была возможность перегрузить js класс редактора (Документация). Не понятно было какой из сотен методов мне нужен.

Методом перебора был найден один интересный метод SAttr в котором происходила установка атрибутов абсолютно всех html элементов внутри редактора.
Он принимает внутрь 3 параметра: Объект, Свойство, Значение.

Приведу пример, в котором после загрузки изображения в редактор происходит вставка превью, и устанавливается ссылка на оригинал (Добавлять надо в bitrix/php_interface/init.php):
<?
//событие загрузки визуального редактора
AddEventHandler("fileman", "OnIncludeHTMLEditorScript", "OnIncludeHTMLEditorHandler"); 

//наш обработчик
function OnIncludeHTMLEditorHandler()
{
 ?>
 <script>
        //запоминаем ссылку на родителя
        SAttr_= SAttr;
 SAttr = function (e,a,v) { //элемента, свойство, значение
          //выполняем сначала родителя (устанавливаем свойства)
          this.SAttr_(e,a,v);
          //произошла установка title изображения
          //title может быть установлен при изменении или загрузке изображения
          if(a == 'title' && e.tagName == 'IMG') {
            var src = e.src;

            //формируем новое изображение
            var img = '';
            if(e.hspace > 0) img = img + ' hspace='+e.hspace;
            if(e.vspace> 0) img = img + ' vspace='+e.vspace;
            if(e.border> 0) img = img + ' border='+e.border;
            if(e.align != "") img = img + ' align="'+e.align+'"';
            //if(e.alt!= "") img = img + ' alt="'+e.alt+'"';
            if(e.title!= "") img = img + ' title="'+e.title+'"';

            //вставляем наше измененное изображение
            //я использовал плагин zoomie для jquery, без собственно ресайза изображения
            pObj.pMainObj.insertHTML('<img alt="'+src+'" class="zoomi" width='+e.width+' height='+e.height+' src="'+src+'" '+img+'>');
            
            //удаляем неизмененное изображение
            e.parentNode.removeChild(e);
          }  
 }
 </script>
 <?
}
?>

Пример работы

воскресенье, 31 января 2010 г.

Vkontakte: безопасная передача данных от клиента к серверу в iFrame приложениях

Не так давно Vkontakte ввело возможность разработки iFrame приложений. В связи с этим у многих разработчиков встал вопрос как защитить данные, передаваемые между приложением и api.

Я бы хотел предложить свое решение:
Защиту данных можно разделить на следующие аспекты:

1. Подтверждение информации о пользователе.
Чтобы получить достоверные подписанные данные о пользователе, необходимо использовать первый запрос к API в настройках приложения.
method=getProfiles&uids={viewer_id}&format=json&v=2.0
Тем самым мы получаем несколько параметров, один из которых нужный нам - viewer_id, а также сигнатуру данных (auth_key), подписанных секретным ключом, который гарантирует нам верность данных.
Т.е. на следующих этапах мы можем использовать информацию о пользователе, не боясь что данные были подменены.

2. Передача данных от пользователя к приложению
Чтобы скрыть секретный ключ и при этом иметь возможность передать данные на сервер придется использовать промежуточное звено - наш ajax обработчик.
Клиентский JavaScript:
function secureSend(_data, _callback) {
  $.ajax({
   type: "POST",
   url: "ajax.php",
   data: (_data),
   dataType: "json",
   success: function(data) {
     if(data.error != undefined) {
        error(data.error);
        return false;
     } else {
      _callback(data.response);
     }
   }
 });
}
На сервере же расположить скрипт обрабатывающий только обращения к API, которые пересылают данные от пользователя к серверу. Разрешить можно все действия, т.к. пользователя мы проверили на предыдущем этапе и уже тут он волен сколько угодно раз пересылать свои голоса к приложению :)
ajax.php, серверный скрипт
$vk_allow_methods = array("secure.withdrawVotes", "secure.getBalance");
$q = $_POST;
$d = "";
if(in_array($q['method'], $vk_allow_methods)) {
  $d = vkSend($q['method']);
}

function vkSend($method) {
  $server = "http://api.vkontakte.ru/api.php?";
  $key = "000";
  $aid = 1;
  
  $qr = array();
  $qr[] = 'method='.$method;
  $qr[] = 'api_id='.$aid;
  $qr[] = 'v=2.0';
  $qr[] = 'format=JSON';
  $qr[] = 'timestamp='.time();
  $qr[] = 'random='.rand(1, 999999);
  $qr[] = 'test_mode=1';
  sort($qr);
  
  $url4sign = implode("", $qr).$key;
  $sign     = md5($url4sign);
  $url      = implode("&", $qr)."&sig=".$sign;
  
  return file_get_contents($server.$url);
}

echo $d;

3. Передача данных от приложения к пользователю.
Такие события не должны ни в коем случае инициироваться клиентским кодом. Если необходимо перевести голоса со счета приложения на счет пользователя, то лучше воспользоваться запароленым скриптом, вызывающимся по cron или вручную администратором.

суббота, 9 января 2010 г.

Flex, PHP: Безопасная передача данных с клиента на сервер

В ходе построения клиент-серверного приложения, требуется реализовать возможность защищенной передачи данных.
В этом случае я использую следующий подход:

1. Подписывание данных секретным ключом.
Это дает гарантию, что данные не будут подменены на промежуточных узлах до сервера.
Технология следующая:

a. Генерируем секретный ключ, о котором знает только приложения клиента и сервера
b. К передаваемым данным на сервер добавляем еще один параметр равный хэшу данных с секретным ключом. Пример на Flex:
var key:String = "0000-0000-0000-0000-0000";
var sreq:String = "method=setBalance&addMoney=1000"; //сам запрос
//отсортируем параметры
//чтобы последовательность параметром и на клиенте и на сервере была одинакова. Это гарантирует схождение хэшей.
sreq = sreq.split("&").sort().join("&");
//добавим к параметрам время, чтобы нельзя было продублировать запрос 
var now:Date = new Date();
var sec = now.getTime().toString().substr(0, -3);
//конечный запрос к серверу с дополнительным параметром = хэшу запроса и секретного ключа
sreq = sreq + "&timestamp="+sec+"&sig="+MD5.hash(sreq+key);
c. Сервер принимает запрос и проверяет данные используя свой секретный ключ.
К сожалению нельзя использовать четкое совпадение времени клиента и сервера, т.к. оно может отличаться или сообщение может идти до нескольких секунд. Из-за этого на сервере допускается расхождение timestamp до 100 секунд. Серверный код на PHP:
$_TIME    = $_SERVER['REQUEST_TIME'];//время запроса
$_KEY     = "0000-0000-0000-0000-0000";//тотже ключ, что и на клиенте
$_TIMEOUT = 100; //допустимое отклонение времени
// Проверим валидность данных
//присланная подпись должна сойтись со сгенерированной на основе закрытого ключа 
$q = $_GET;
//отсортируем также как в flex
unset($q['timestamp'], $q['sig']);
ksort($q);
$req = "";
foreach($q as $k=>$v) {
  $req .= $k."=".$v."&";
}
$req = substr($req, 0, -1);

if($_GET['sig'] != md5($req.$_KEY)) {
  //подписи не сошлись. Данные были подменены.
  exit;
}
//Проверим расхождение времени
if($_GET['timestamp'] + $_TIMEOUT < $_TIME) {
  //Расхождение более чем на 100 секунд. Прерываем запрос
  exit;
}

К сожалению такой метод не гарантирует нам, что запрос не будет перехвачен на промежуточном узле и не продублирован N раз в течении этих 100 секунд. Для устранения этого служит следующий пункт:

2. Проверка запроса на дублирование.
Идею я позаимствовал из TCP/IP. Добавим к запросу еще один параметр = счетчику сообщений, а на сервере будем хранить значение последнего счетчика. На клиенте достаточно сгенерировать счетчик = текущее значение + 1, а на сервере принять, проверить что новое значение больше старого и обновить серверный счетчик. Это гарантирует нам, что если злоумышленник перехватит запрос и пошлет его заново, то сервер его отвергнет, т.к. значение будет меньше хранящегося на сервере.
Для реализации этого механизма на сервере нам уже понадобится БД, а на клиенте дополнительный запрос на получение счетчика.

Под катом можно увидеть полный код клиента и сервера:

суббота, 7 ноября 2009 г.

Модуль связи PunBB 1.3 и Drupal 6

Модуль связи PunBB и Drupal
Изменения:
1. Переработка модуля для версии Drupal 6
2. Усложнение авторизации и регистрации форума punbb привело к изменению модуля.

Напомню еще раз характеристики:
Только через Drupal. Рекомендую на пунбб вообще отключить регистрацию.
1. Единая регистрация
2. Единая авторизация
3. Единый выход
4. Единая смена учетных данных (В данной версии: почта, временная зона и пароль)
5. Анонс нодов на форуме

Доступ внутри node к информации о топике:
$node['punbb'] == array('id', //ид топика
                        'subject', //тема топика
                        'posted', //автор топика
                        'url'); //url до топика на форуме

Дата разработки: ноябрь 2009
Основное место обсуждения

Скачать архив (Размер файла: 8КБ)

четверг, 29 октября 2009 г.

Продажа контента за SMS

Сейчас очень распространенным стала оплата услуг и товаров через СМС. Но не многие знают как это все организовано.
Я бы хотел рассказать об одном из простых способов.

Рассмотрим пример оплаты и прокомментируем каждый этап исходным кодом на PHP.

0. Размещению на сайте оплаты через смс предшествует заключение договора с компанией предоставляющей эти услуги.
1. Пользователь заходит на страничку, где ему предлагается скачать приложение, предварительно отослав смс с текстом на указанный номер.
Реализация: Выводится статичные текст с текстом и телефоном, который предоставила на предыдущем этапе организация
2. Клиент отправляет СМС. Ему приходит ответная с уникальным кодом на доступ
Реализация: Сервер смс компании, получает сообщение с нужным тектом на номер. Генерируется событие, вызывающее произвольную (настраиваемую) страницу на нашем сайте. На это странице мы произвольны сделать, что угодно.
Генерируем код, и записываем в БД:
function getCOde() {
  $code = sprintf("%06x", rand(1000000, 10000000)); //рандомное число, преобразованное в hex
  //проверяем на повторения
  $res = db_query("SELECT COUNT(*) AS cnt FROM {sms_pay} WHERE code = '%s'", $code);
  $codes = db_fetch_array($res);
  if($codes['cnt'] > 0) {
    getCode(); //генерируем еще раз
  }
  db_query("INSERT INTO {sms_pay} (code) VALUES('%s')", rand(), $code); //записываем код в БД
  return $code;
}
Сгенерированный код, вываливается через echo в браузер, а сервер весь сгенерированый текст отправляет в виде смс обратно клиенту.
На данном этапе код еще не привязан ни к одному материалу и его можно активировать на любом.
3. Клиент получил код, вводит его, чтобы получить доступ к материалу
Реализация: Сервер сайт получает код, проверяет его наличие в БД. Если код верный, то в БД вносятся данные о файле к которому был получен доступ, также для защиты от передачи кода третьим лицам в бд заносится ip адрес клиента и ставится кука.
Файл может быть скачан не сразу, а по частям. В этом случае пользователю дается несколько часов на скачку файла: в бд заносим также время первого обращения к файлу.
После этого пользователю файл стримится прямо в браузер с утановкой необходимых хеадреов, чтобы у клиента не было прямого доступа к файлу (Об этом можно почитать отдельно, я пользуюсь стандартными функциями Drupal)
4. Клиент повторно обращается к этому же файлу.
Реализация: При повторном обращении к файлу, снова проверять код не обязательно. Необходимо сверить лишь куки и ip пользователя, а также id файла к которому идет обращение.
5. Если пользователь пытается обратится к другому файлу с этим же кодом, то это ему не удастся, т.к. он не пройдет проверку по id файла.
6. Код был передан третьим лицам. Пользователь не пройдет проверку по ip и куке.
Примерный код для предыдущих пунктов можно видеть в листинге: (php/Drupal, точка входа функция sms_pay_file_download)

суббота, 22 августа 2009 г.

Конструктор тестов с экпортом

Конструктор тестов/опросов/голосовании с возожностью экспорта на стороние сайты (самих тестов и статистики)
Характеристики: php, mysql, ajax, jsonp, jquery
Дата разработки: март-май 2009

Скачать архив (Размер файла: 59 КБ)

SimplePDO

Класс расширяющий и упрощающий работу с PDO
Синтаксис:
$r = $sp->query("insert into ?_url_alias(src, dst) VALUES(%s, %s)", "tst", "test");
echo $sp->query("update ?_url_alias set dst = %s where pid = %u", "tst1", $r);
print_r($sp->selectCol("{?_url_alias} select * from ?_url_alias"));
print_r($sp->selectRow("{?_url_alias} select * from ?_url_alias where pid = %u and dst = %s", 1, "news"));
print_r($sp->select("select * from ?_url_alias"));
Возможности: плэйсхолдеры, кэширование, перфиксы таблиц, выборка таблиц/строк/ячеек/столбцов
Дата разработки: июль 2008

Скачать архив (Размер файла: 4КБ)

Шаблонизатор SimpleTpl

Простой шаблонизатор.
Пример использования:
Read Template and set Variables
->getTpl(string Tpl [, mixed Variable1 [, mixed Variable2] [, ...]]);
return: String template with replace variables. Exit program, if template not exist
Дата разработки: июль 2008

Скачать архив (Размер файла: 915Б)

Модуль связи PunBB и Drupal

Модуль связи PunBB и Drupal
Только через Drupal. Рекомендую на пунбб вообще отключить регистрацию.
1. Единая регистрация
2. Единая авторизация
3. Единый выход
4. Единая смена учетных данных (В данной версии: почта, временная зона и пароль)
5. Анонс нодов на форуме
Дата разработки: февраль - апрель 2008
Основное место обсуждения

Скачать архив (Размер файла: 8КБ)

WikiMedia

Wiki движок
Особенность от обычного wiki: возможность создания виджетов (программ написанных на внутреннем языке движка (подмножестве PHP)), с целью автоматизации процессов внутри сайта
Основные виджеты: вставка аудио/видео, сжатие картинок, преобразование ссылок (виджеты можно создавать самому)
Характеристики: php/mysql/mod_rewrite
Дата разработки: октябрь-декабрь 2007

Скачать архив (размер файла: 3221КБ, внутри есть полное описание проекта)

PHP-CRAFT

Портальная система для создания сайтов:
Харктеристики: PHP/MYSQL/MOD_REWRITE/модульность/блочность/смена тем/wysiwig
Дата создания: 2004-2006 года

Скачать файл (Размер архива: 1043КБ)