Всем хорошего дня.
Пишу в данный момент для modx с использованием индивидуальных документов для каждого обычного пользователя.
Поскольку забивать этими документами админку не хотелось (если с сайтом все нормально будет, их там будет многовато и админка начнет тормозить), решил вынести их в отдельную таблицу БД, с выводом через один ресурс.
Итак, рассмотрим наши требования к документу (по крайне мере, у меня были такие):
- У документа должны быть заголовок и содержание;
- У документа должен быть тип (для более простого поиска документов одного типа);
- У владельца документа всегда есть доступ к его редактированию и просмотру;
- У владельца сайта и его юристов есть произвольный доступ к любому из документов;
- Владелец сайта и те, кому он это разрешил, должны иметь возможность выдавать нужным им пользователям права на просмотр и редактирование произвольного документа;
- Произвольный зарегистрированный пользователь может получить право на только просмотр либо также и редактирование произвольного документа на время либо сразу навсегда.
1. Определение структуры данных
Для начала прикинем структуру записи документа в базе данных:
//Сниппет TryLoadDocument
<?php
/**
Итак, все документы хранятся в базе вот в таком виде(переделано в формат JSON для лучшей читаемости):
{
"type":"agreement", - тип документа, текстовый, короткий, примеры: carta, license и т.д.
"title":"Договор", - заголовок документа
"text":"Пример. Текст договора. Подпишите здесь: ______ ", - текст документа, XSS контролирует CLeditor, не наша забота. Содержит весь текст какого-то документа со всеми тегами разметки.
"owner":29, - это владелец документа, то есть тот, с кем наш сайт его заключил. у него в любом случае есть право смотреть и редактировать этот документ(т.к. он "его")
"edit":[29,555,34,52], - это те, кто может редактировать документ.
!ВАЖНО! пользователи групп Administrator,Jurists имеют доступ к ЛЮБОМУ документу!
"view":[5677,599677,5999898677,855677] - те, кто открыв страничку http://.../docs?doc_id=5 увидят этот документ(но редактировать не смогут)
"view-temp":[{"id":5,"until":1413640050},{"id":9,"until":1413640100},{"id":7,"until":1413640050}] - аналогично view, но "until"(формат timestamp) указывает, вплоть до какого момента времени нужно учитывать эту запись(после этого момента она удаляется)
"edit-temp":[{"id":5,"until":1413640050},{"id":9,"until":1413640100},{"id":7,"until":1413640050}] - аналогично edit, но "until"(формат timestamp) указывает, вплоть до какого момента времени нужно учитывать эту запись(после этого момента она удаляется)
}
вот таким запросом можно такую таблицу создать:
CREATE TABLE IF NOT EXISTS `documents` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id документа',
`type` varchar(255) NOT NULL COMMENT 'тип документа',
`title` varchar(255) NOT NULL COMMENT 'заголовок документа',
`text` text NOT NULL COMMENT 'текст документа',
`owner` bigint(20) NOT NULL COMMENT 'владелец дока',
`edit` text NOT NULL COMMENT 'все юзеры, у кого есть права на просмотр и редактирование',
`edit-temp` text NOT NULL COMMENT 'временные разрешения на редактирование',
`view` text NOT NULL COMMENT 'все юзеры, у кого есть права на просмотр',
`view-temp` text NOT NULL COMMENT 'временные разрешения на редактирование',
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=cp1251 COMMENT='Личные документы пользователей - здесь...';
*/
2. Сниппет TryLoadDocument
2.1 Обеспечение переносимости
После определения структуры стало понятно, что делать это набором функций будет спорным решением, поэтому был выбран вариант с ООП.
Ну и на этом же этапе вспоминаем, что объекты удобно повторно использовать в других модулях(сниппетах, в данном случае), поэтому используем сохранение этого сниппета в статичном файле docs.php(что также дает возможность в дальнейшем быстро подключить аякс, если будет такая необходимость), а также добавляем проверку, не подключен ли этот сниппет к другому только ради нашего класса вот таким образом:
<?php
class Document {...}
if(DOC_API_MODE!="API")//если нужен только класс Document, эта часть кода не выполнится)
{
...// выполнение кода сниппета
}
Теперь для подключения класса Document достаточно в другом сниппете напиcать:
define("DOC_API_MODE","API");
include_once 'docs.php';
2.2 Пишем общую логику работы класса
Теперь, когда все приготовления сделаны, начнем работу с самим классом Document.
Сначала определим данные нашего документа:
private $data;//здесь все данные документа в виде ассоциативного массива(напр $data['title'] - заголовок документа)
private $uid;//=user id — id того, кто пытается использовать этот документ(или создать)
private $modx;//для работы с modx API
В конструкторе мы будем только узнавать кто нас грузит, а не что за документ ему нужен, что позволит, например, используя один и тот же объект класса, создать множество документов пользователю сразу.
public function __construct($modx)
{//т.к. доступа напрямую к $modx в функциях нет(по крайней мере у меня), передадим ее параметром
$this->modx=$modx;
$this->uid=$this->modx->user->get('id');//объект документа можно создать только от имени текущего юзера
}
Дальше прикинем, что мы хотим чтобы объект этого класса нам выдавал? Другими словами, публичные методы.
Очевидно, это:
- Заголовок и содержание документа;
- Сможет ли текущий пользователь увидеть документ?
- Сможет ли текущий пользователь отредактировать документ?
- Есть ли у него право на выдачу прав другим?
Также добавим сюда то, что мы хотим, чтобы этот класс давал возможность делать:
- Загрузить документ по его номеру(id) — будет дальше в тексте, т.к работа напрямую с БД;
- Сохранить документ в текущем виде — будет дальше в тексте, т.к работа напрямую с БД;
- Создать новый документ, задается заголовок, текст, и возможность его редактировать.
Вот реализация данных функций:
public function &__set ( $name , $value )
{//снаружи(не используя функций) менять можно только title и text
$allowed=["title","text"];
if(in_array($name,$allowed))
{
if($this->editAllowed())//если текущему пользователю можно редактировать
$this->data[$name]=$value;
}
}
public function &__get ( $name )
{//снаружи класса напрямую получить значения полей могут все из списков edit & view, а также владелец...
if($this->isOwner() || $this->viewAllowed() || $this->editAllowed())
{
switch ($name)
{
case "title":return $this->data['title'];
case "text": return $this->data['text'];
case "id": return $this->data['id'];
case "uid": return $this->uid;
}
}
return 'forbidden';
}
Теперь проверки на разрешения + создание нового документа из заданных данных:
public function MakeNew($type,$title,$text)//для создания документа по шаблону, либо сгенерированного динамически
{
$this->data['text']=$text;//текст документа
$this->data['title']=$title;//заголовок
$this->data['view']=[];//кому можно просматривать документ
$this->data['view-temp']=[];
$this->data['edit']=[];//кому можно документ редактировать
$this->data['edit-temp']=[];
$this->load($this->saveAsNew($type));//сохраняем док и загружаем его в нормальном виде
}
public function viewAllowed()
{//возвращает истину, если этому юзеру можно просматривать этот документ, иначе - ложь..
$allowed= $this->isOwner()//если запрашивающий - создатель документа
|| in_array($this->uid,$this->data['view']);//или имеет право на его просмотр
if($allowed)
return true;
else
for($i=0; $i<count($this->data['view-temp']);$i++)
if($this->data['view-temp'][$i]->id==$this->uid)
return $this->data['view-temp'][$i]->until > time();
return false;
}
public function editAllowed()
{//возвращает истину, если этому юзеру можно редактировать этот документ, иначе - ложь..
$allowed = $this->isOwner() || //если запрашивающий - создатель документа
in_array($this->uid,$this->data['edit']);//если запрашивающему можно редактировать документ
if($allowed)
return true;
else
for($i=0; $i<count($this->data['edit-temp']);$i++)
if($this->data['edit-temp'][$i]->id==$this->uid)
return $this->data['edit-temp'][$i]->until > time();
//доступ к временным полям через '->' из-за принципа декодирования функцией json_decode
return false;
}
public function manageAllowed()
{//true если этому юзеру можно давать другим доступ к документу. false в обратном случае.
return $this->modx->user->isMember('Jurists')||$this->modx->user->isMember('Administrator');
}
public function allow($new_user,$can_edit,$time=0)
{
//дать кому-то доступ к документу. $new_user - кому дать,
//$can_edit - может ли редактировать(только если запросивший сам может редактировать, иначе не сработает)
//$time - на какое время дать права(по умолчанию 0 - навсегда. измеряется в секундах
$user_id=(int)$new_user;
if($user_id!=0 && $this->manageAllowed())
if($can_edit)
{
if($this->editAllowed())
{
if($time==0)//выдать права навсегда
$this->data['edit'][]=$user_id;
else//выдать права на $time секунд
$this->data['edit-temp'][]=["id"=>$user_id,"until"=>time()+$time];
}
}
else
{
if($time==0)//выдать права навсегда
$this->data['view'][]=$user_id;
else//выдать права на $time секунд
$this->data['view-temp'][]=["id"=>$user_id,"until"=>time()+$time];
}
}
public function isOwner()
{
$usual=$this->uid==$this->data['owner'];//права владельца есть у:
if($usual) return true;//самого владельца,
else//спец аккаунтов
return $this->manageAllowed();
}
Вспомогательная функция, очищающая документ от устаревших временных разрешений:
private function clearTemp()
{//очищает все массивы от врeменных разрешений у которых прошел срок действия
if(count($this->data['view-temp'])+count($this->data['edit-temp']) > 0)//если хоть какие-то временные данные есть
{
for($i=0; $i<count($this->data['view-temp']);$i++)
//удалить все временные разрешения, у которых дата истечения раньше текущего времени(time())
{
if($this->data['view-temp'][$i]->until < time())
unset($this->data['view-temp'][$i]);
}
$this->data['view-temp']=array_values($this->data['view-temp']);//просто фикс проблемы с индексами( [1,3,5]=>[0,1,2)]
for($i=0; $i<count($this->data['edit-temp']);$i++)
//удалить все временные разрешения, у которых дата истечения раньше текущего времени(time())
{
if($this->data['edit-temp'][$i]->until < time())
unset($this->data['edit-temp'][$i]);
}
$this->data['edit-temp'] = array_values($this->data['edit-temp']);//просто фикс проблемы с индексами( [1,3,5]=>[0,1,2)]
$this->save();//сохранить изменения
}
}
2.3 Пишем функции для работы с конкретной базой данных.
Итак, логическая часть нашего кода, не зависящая от конкретной бд, завершена. Теперь перейдем на уровень ниже, работе напрямую с бд в MODX с использованием xPDO.
public function load($id)
{//загружает документ из бд(на это прав хватит у любого, вот только не любой сможет эти данные у класса получить)
$sql="SELECT * FROM `documents` WHERE `id`=:id";
$query = new xPDOCriteria($this->modx, $sql,array(':id'=>$id));
if($query->prepare() && $query->stmt->execute())
{//если данные удачно загружены
$this->data = $query->stmt->fetchAll(PDO::FETCH_ASSOC)[0];
if(count($this->data)==0) return false;//если пришел пустой ответ, сообщаем о фейле зуагрузки
$this->data['edit']=json_decode($this->data['edit']);///распаковываем список имеющих право на редактирование в массив
$this->data['edit-temp']=json_decode($this->data['edit-temp']);///распаковываем список имеющих временное право на редактирование в массив
$this->data['view']=json_decode($this->data['view']);///распаковываем список имеющих право на просмотр в массив
$this->data['view-temp']=json_decode($this->data['view-temp']);///распаковываем список имеющих временное право на просмотр в массив
$this->clearTemp();//очищаем массивы view-temp & edit-temp от закончившихся разрешений
return true;//раз дошли сюда, сообщаем, что все ок
}
else return false;//если не выполнился запрос, сообщаем о фейле загрузки
}
public function save()
{//сохраняет новое значение документа в бд
$sql="UPDATE `documents` SET `title`=:title, `text`=:text, `view`=:view, `edit`=:edit, `view-temp`=:viewtemp, `edit-temp`=:edittemp WHERE `id`=:id";//шаблон запроса
$this->data['view']=json_encode($this->data['view']);///запаковываем список имеющих право на просмотр в строку
$this->data['view-temp']=json_encode($this->data['view-temp']);///запаковываем список имеющих право на временный просмотр в строку
$this->data['edit']=json_encode($this->data['edit']);///запаковываем список имеющих право на редактирование в строку
$this->data['edit-temp']=json_encode($this->data['edit-temp']);///запаковываем список имеющих право на временное редактирование в строку
$query=new xPDOCriteria($this->modx, $sql,
[":title"=>$this->data['title'],":text"=>$this->data['text'],":edit"=>$this->data['edit'],":view"=>$this->data['view'],":edittemp"=>$this->data['edit-temp'],":viewtemp"=>$this->data['view-temp'],":id"=>$this->data["id"]]);//подставляем данные
$query->prepare() && $query->stmt->execute();//выполняем запрос
//преобразуем строки обратно в массивы
$this->data['view']=json_decode($this->data['view']);
$this->data['view-temp']=json_decode($this->data['view-temp']);
$this->data['edit']=json_decode($this->data['edit']);
$this->data['edit-temp']=json_decode($this->data['edit-temp']);
}
private function saveAsNew($type)
{//сохраняет уже заполненный документ как новую запись типа $type
$sql="INSERT INTO `documents` (`title`,`text`,`view`,`edit`,`owner`,`type`) VALUES(:title,:text,:view,:edit,:uid,:type)";//шаблон запроса
$this->data['view']=json_encode($this->data['view']);///запаковываем список имеющих право на просмотр в строку
$this->data['edit']=json_encode($this->data['edit']);///запаковываем список имеющих право на редактирование в строку
$this->data['view-temp']=json_encode($this->data['view-temp']);///запаковываем список имеющих право на просмотр в строку
$this->data['edit-temp']=json_encode($this->data['edit-temp']);///запаковываем список имеющих право на редактирование в строку
$query=new xPDOCriteria($this->modx, $sql,
[":title"=>$this->data['title'],":text"=>$this->data['text'],":edit"=>$this->data['edit'],":view"=>$this->data['view'],":uid"=>$this->uid,":type"=>$type]);//подставляем данные
//логгируем создание нового документа, полезно для отладки и чтобы посмотреть как шаблон данными заполняется
//$this->modx->log(modX::LOG_LEVEL_ERROR,"Выполнение запроса: ".$query->toSQL());
$query->prepare(); $query->stmt->execute();//выполняем запрос
return $this->modx->lastInsertId();//вернем id созданного дока
}
Вот и весь класс Document. Довольно простой, имхо.
2.4 Исполняемый код сниппета
Теперь разберемся, что же делает наш сниппет TryLoadDocument?
Итак…
if($modx->user->get('id')==0)//если на страницу зашел анон,
{
$modx->sendRedirect($modx->makeUrl(14));//отправляем его на страницу номер 14, именно там у нас вход в систему.
exit;//на случай если редирект ВДРУГ не сработал, завершаем скрипт наверняка(вместо странички будет просто белый экран)
}
$doc=new Document($modx);//создаем объект для работы с документом для вошедшего на страницу пользователя
if(!$doc->load($_GET['doc_id']))//пытаемся загрузить док из бд
{//если не удалось, выходим...
return 'Документ не найден...';
}
//!!! сообщаем чанку Rights, отображать ли поле редактирования доступом
$modx->setPlaceholder('CanManage',$doc->manageAllowed()?'true':'false');
//!!! сообщаем чанку DocText, отображать ли форму редактирования
$modx->setPlaceholder('CanEdit',$doc->editAllowed()?'true':'false');
function DataToUID($userstr,$modx)//для вытаскивания цифры с номером пользователя из поля "пользователю"
{
$link_rgx="/(.*uid=)([d]{1,})(.*)/";//регулярка для ссылки вида http://*путь до личного кабинета*?uid=56
$id_rgx="/([^w]{0,})([d]{1,})/";//регулярка id пользователя(до и после числа могут быть пробелы)
if(preg_match($link_rgx,$userstr))
{//для ссылки вида http://*путь до личного кабинета*?uid=56
$r="$2";
return preg_replace($link_rgx,$r,$userstr);
} else
if(preg_match($id_rgx,$userstr))//если ввод был " 234", например
{
$r="$2";
return preg_replace($id_rgx,$r,$userstr);//вернем только число
} else//это и не идентификатор юзера и не ссылка на его лк? тогда может это ник?
{
$usr=$modx->getObject('modUser',["username"=>$userstr]);//пытаемся найти юзера с таким ником
return $usr?$usr->get('id'):'-1';
}
}
if(isset($_POST['add']))//если юзер нажал кнопочку "добавить права пользователю...
{
$userID=(int)DataToUID($_POST['userid'],$modx);//вытаскиваем id пользователя из поля формы 'userid'
if($userID!='-1')//если пользователь нашелся
{
if($_POST['allow']=="edit")//если надо добавить права на редактирование и просмотр(задается полем формы 'allow')
{
//пишем в лог
$modx->log(modX::LOG_LEVEL_ERROR,"Попытка выдать права на редактирование документа #".$doc->id." пользователем #".$doc->uid." пользователю #".$userID);
$doc->allow($userID,true,(int)$_POST['length']);
$doc->save();
} else
if($_POST['allow']=="view")////если надо добавить права на просмотр(задается полем формы 'allow')
{
//пишем в лог
$modx->log(modX::LOG_LEVEL_ERROR,"Попытка выдать права на просмотр документа #".$doc->id." пользователем #".$doc->uid." пользователю #".$userID);
$doc->allow($userID,false,(int)$_POST['length']);
$doc->save();
}
}else $modx->log(modX::LOG_LEVEL_ERROR,"DataToUID не справилась. (".$_POST['userid'].")");//если вместо имени пользователя передали фигню, пишем в лог
}
if(isset($_POST['edit']))//если юзер отредактировал документ и нажал "сохранить"
{
$modx->log(modX::LOG_LEVEL_ERROR,"Попытка отредактировать текст документа #".$doc->id." пользователем #".$doc->uid);//пишем в лог
if(!empty($_POST["text"]))//если новый вариант документа не пуст(нету смысла в пустых доках)
{
$doc->text=$_POST["text"];//задаем полю класса text новое значение
$doc->save();
$modx->log(modX::LOG_LEVEL_ERROR,"Отредактирован текст документа #".$doc->id." пользователем #".$doc->uid);//пишем в лог
}
}
/**********Вывод данных в форму*************/
if(!isset($_POST['ajax']))//если мы вызваны не через аякс, а для загрузки страницы.
{
$output="";
//грузим док, если нельзя, то объект сам разберется
//передаем чанку заголовка DocTitle параметром свойство title, то есть заголовок документа. проверять доступ к нему - не наша забота.
$output.=$modx->getChunk('DocTitle',['title'=>$doc->title]);
//передаем чанку заголовка DocText параметром свойство title, то есть заголовок документа. проверять доступ к нему - не наша забота.
$output.=$modx->getChunk('DocText',['text'=>$doc->text]);
$modx->setPlaceholder('doc', $output);
return '';
}
Для лучшего понимания кому-то может быть удобнее посмотреть код в один блок:
if($modx->user->get('id')==0)//если на страницу зашел анон,
{
$modx->sendRedirect($modx->makeUrl(14));//отправляем его на страницу номер 14, именно там у нас вход в систему.
exit;//на случай если редирект ВДРУГ не сработал, завершаем скрипт наверняка(вместо странчики будет просто белый экран)
}
/*******/
$doc=new Document($modx);//создаем объект для работы с документом для вошедшего на страницу пользователя
if(!$doc->load($_GET['doc_id']))//пытаемся загрузить док из бд
{//если не удалось, выходим...
return 'Документ не найден...';
}
//!!! сообщаем чанку Rights, отображать ли поле редактирования доступом
$modx->setPlaceholder('CanManage',$doc->manageAllowed()?'true':'false');
//!!! сообщаем чанку DocText, отображать ли форму редактирования
$modx->setPlaceholder('CanEdit',$doc->editAllowed()?'true':'false');
function DataToUID($userstr,$modx)//для вытаскивания цифры с номером пользователя из поля "пользователю"
{
$link_rgx="/(.*uid=)([d]{1,})(.*)/";//регулярка для ссылки вида http://*путь до личного кабинета*?uid=56
$id_rgx="/([^w]{0,})([d]{1,})/";//регулярка id пользователя(до и после числа могут быть пробелы)
if(preg_match($link_rgx,$userstr))
{//для ссылки вида http://*путь до личного кабинета*?uid=56
$r="$2";
return preg_replace($link_rgx,$r,$userstr);
} else
if(preg_match($id_rgx,$userstr))//если ввод был " 234", например
{
$r="$2";
return preg_replace($id_rgx,$r,$userstr);//вернем только число
} else//это и не идентификатор юзера и не ссылка на его лк? тогда может это ник?
{
$usr=$modx->getObject('modUser',["username"=>$userstr]);//пытаемся найти юзера с таким ником
return $usr?$usr->get('id'):'-1';
}
}
/*********Эти два для работы в режиме форм***********/
if(isset($_POST['add']))//если юзер нажал кнопочку "добавить права пользователю...
{
$userID=(int)DataToUID($_POST['userid'],$modx);//вытаскиваем id пользователя из поля формы 'userid'
if($userID!='-1')//если пользователь нашелся
{
if($_POST['allow']=="edit")//если надо добавить права на редактирование и просмотр(задается полем формы 'allow')
{
//пишем в лог
$modx->log(modX::LOG_LEVEL_ERROR,"Попытка выдать права на редактирование документа #".$doc->id." пользователем #".$doc->uid." пользователю #".$userID);
$doc->allow($userID,true,(int)$_POST['length']);
$doc->save();
} else
if($_POST['allow']=="view")////если надо добавить права на просмотр(задается полем формы 'allow')
{
//пишем в лог
$modx->log(modX::LOG_LEVEL_ERROR,"Попытка выдать права на просмотр документа #".$doc->id." пользователем #".$doc->uid." пользователю #".$userID);
$doc->allow($userID,false,(int)$_POST['length']);
$doc->save();
}
}else $modx->log(modX::LOG_LEVEL_ERROR,"DataToUID не справилась. (".$_POST['userid'].")");//если вместо имени пользователя передали фигню, пишем в лог
}
if(isset($_POST['edit']))//если юзер отредактировал документ и нажал "сохранить"
{
$modx->log(modX::LOG_LEVEL_ERROR,"Попытка отредактировать текст документа #".$doc->id." пользователем #".$doc->uid);//пишем в лог
if(!empty($_POST["text"]))//если новый вариант документа не пуст(нету смысла в пустых доках)
{
$doc->text=$_POST["text"];//задаем полю класса text новое значение
$doc->save();
$modx->log(modX::LOG_LEVEL_ERROR,"Отредактирован текст документа #".$doc->id." пользователем #".$doc->uid);//пишем в лог
}
}
/**********Вывод данных в форму*************/
if(!isset($_POST['ajax']))//если мы вызваны не через аякс, а для загрузки страницы.
{
$output="";
//грузим док, если нельзя, то объект сам разберется
//передаем чанку заголовка DocTitle параметром свойство title, то есть заголовок документа. проверять доступ к нему - не наша забота.
$output.=$modx->getChunk('DocTitle',['title'=>$doc->title]);
//передаем чанку заголовка DocText параметром свойство title, то есть заголовок документа. проверять доступ к нему - не наша забота.
$output.=$modx->getChunk('DocText',['text'=>$doc->text]);
$modx->setPlaceholder('doc', $output);
return '';
}
return 'Ошибка...';//для нормального куда мы здесь вообще не должны оказаться, так что вернем хоть что-то для объяснения неполадки
3. Форматирование полученных данных и вывод на страницу
3.1 Необходимые чанки
Для правильного вывода документа используются три простых чанка:
<form action="[[~[[*id]]]]?doc_id=[[!GET? ¶m=`doc_id`]]" method="post" >
<fieldset><span>Выдать права пользователю...</span><input type="text" name="userid" style="
background: white;
width: 340px;
padding: 5px;
font-size: 14px;
margin: 0 15px;
"/>
на
<select name="allow">
<option value="view" selected="selected">просмотр</option>
<option value="edit">просмотр и редактирование</option>
</select>
<select name="length">
<option value="60">на 1 минуту.</option>
<option value="600">на 10 минут.</option>
<option value="3600">на час.</option>
<option value="86400">на день.</option>
<option value="0">навсегда.</option>
</select>
<input type="hidden" name="add" value="1" />
</fieldset>
<input type="submit" value="Выдать права!"/>
</form>
<h2>[[!+title]]</h2>
[[!If? &subject=`[[!+CanEdit]]` &operator=`EQ` &operand=`true` &then=`<form action="[[~[[*id]]]]?doc_id=[[!GET? ¶m=`doc_id`]]" method="post">
<input type="submit" value="Сохранить новый текст договора..."/>`]]
<textarea id="text" name="text" >
[[+text]]
</textarea>
[[!If? &subject=`[[!+CanEdit]]` &operator=`EQ` &operand=`true` &then=`<input type="hidden" name="edit" value="1"/>
<input type="submit" value="Сохранить новый текст договора..."/>
</form>`]]
* — если почему-то форма не выводится никогда, стоит проверить, а установлен ли компонент If?
Итоговый код сниппета TryLoadDocument можно посмотреть тут: pastebin.com/R55bPUCH
Ну вот, почти все готово, осталось только приделать это в ваш контент ресурса с вашим шаблоном.
3.2 Ресурс для работы с документом
Код в самом поле контента для ресурса будет таким:
[[!TryLoadDocument]]
[[!If? &subject=`[[!+CanManage]]` &operator=`EQ` &operand=`true` &then=`[[$Rights]]`]]
[[!+doc]]
[[!If? &subject=`[[!+CanManage]]` &operator=`EQ` &operand=`true` &then=`[[$Rights]]`]]
Textarea с текстом нашего документа(из чанка DocText) должна быть завернута в какой-либо WYSIWYG-редактор, я, например, использовал CLeditor.
Добавляете в шаблон такие заголовки в <head>*
<link rel="stylesheet" type="text/css" href="jquery.cleditor.css" />
<script type="text/javascript" src="jquery-2.1.1.min.js"></script> <!--этого по ссылке нет, здесь просто пишите путь до вашего jquery-->
<script type="text/javascript" src="jquery.cleditor.min.js"></script>
<script type="text/javascript">$(document).ready(function () { $("#text").cleditor({ height:"1300px"}); }); </script>
<!--height:"1300px" — для того, чтобы документ не сжимался в слишком мелкий прямоугольник -->
* — для этого я определил TV ExtraHeaders, добавил его в <head> в шаблоне, и там, гле нужны были доп заголовки ресурсу, переопределял их там.
4. Пример использования класса Document в другом сниппете
Допустим, вы хотите в другим сниппете создавать свои документы текущему пользователю:
<?php
...
define("DOC_API_MODE","API");
include_once 'docs.php';
...
$doc= new Document($modx);
$title="Договор";
$text=$modx->getChunk('agreement_template');//пусть у нас есть некий чанк с шаблоном документа
//в этом же вызове ему можно было передать какие-то параметры
$doc->MakeNew('agreement',$title,$text);//готово, мы создали документ
...
В этой статье я старался показать, как идея переходит в логику, далее в код и в итоге — в работающий механизм.
Надеюсь, кому-то это будет полезно.
Заранее извиняюсь за ошибки в тексте статьи (хоть я и старался все отловить). Опыта написания больших текстов у меня нет. Буду рад любым замечаниям.
Автор: MaximChistov