Всем хорошего дня.
Пишу в данный момент для modx с использованием индивидуальных документов для каждого обычного пользователя.
Поскольку забивать этими документами админку не хотелось (если с сайтом все нормально будет, их там будет многовато и админка начнет тормозить), решил вынести их в отдельную таблицу БД, с выводом через один ресурс.
Итак, рассмотрим наши требования к документу (по крайне мере, у меня были такие):
Для начала прикинем структуру записи документа в базе данных:
После определения структуры стало понятно, что делать это набором функций будет спорным решением, поэтому был выбран вариант с ООП.
Ну и на этом же этапе вспоминаем, что объекты удобно повторно использовать в других модулях(сниппетах, в данном случае), поэтому используем сохранение этого сниппета в статичном файле docs.php(что также дает возможность в дальнейшем быстро подключить аякс, если будет такая необходимость), а также добавляем проверку, не подключен ли этот сниппет к другому только ради нашего класса вот таким образом:
Теперь для подключения класса Document достаточно в другом сниппете напиcать:
Теперь, когда все приготовления сделаны, начнем работу с самим классом Document.
Сначала определим данные нашего документа:
В конструкторе мы будем только узнавать кто нас грузит, а не что за документ ему нужен, что позволит, например, используя один и тот же объект класса, создать множество документов пользователю сразу.
Дальше прикинем, что мы хотим чтобы объект этого класса нам выдавал? Другими словами, публичные методы.
Очевидно, это:
Также добавим сюда то, что мы хотим, чтобы этот класс давал возможность делать:
Вот реализация данных функций:
Итак, логическая часть нашего кода, не зависящая от конкретной бд, завершена. Теперь перейдем на уровень ниже, работе напрямую с бд в MODX с использованием PDO.
Вот и весь класс Document. Довольно простой, имхо.
Теперь разберемся, что же делает наш сниппет TryLoadDocument?
Итак…
Для лучшего понимания кому-то может быть удобнее посмотреть код в один блок:
Для правильного вывода документа используются три простых чанка:
* — если почему-то форма не выводится никогда, стоит проверить, а установлен ли компонент If?
И простейший сниппет для получения параметра из адресной строки:
Итоговый код сниппета TryLoadDocument можно посмотреть тут: pastebin.com/R55bPUCH
Ну вот, почти все готово, осталось только приделать это в ваш контент ресурса с вашим шаблоном.
Код в самом поле контента для ресурса будет таким:
Textarea с текстом нашего документа(из чанка DocText) должна быть завернута в какой-либо WYSIWYG-редактор, я, например, использовал CKeditor.
Допустим, вы хотите в другим сниппете создавать свои документы текущему пользователю:
В этой статье я старался показать, как идея переходит в логику, далее в код и в итоге — в работающий механизм.
Надеюсь, кому-то это будет полезно.
Заранее извиняюсь за ошибки в тексте статьи (хоть я и старался все отловить). Опыта написания больших текстов у меня нет. Буду рад любым замечаниям.
Пишу в данный момент для 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(что также дает возможность в дальнейшем быстро подключить аякс, если будет такая необходимость), а также добавляем проверку, не подключен ли этот сниппет к другому только ради нашего класса вот таким образом:
Проверяем, не используется ли сниппет в качестве источника класса Document
<?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 с использованием PDO.
Работа напрямую с базой данных
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?
Итак…
Выполняющийся код сниппета TryLoadDoc — разбор
Проверяем, не зашел ли к нам аноним(его id — 0), в этому случае посылаем его логиниться.
if($modx->user->get('id')==0)//если на страницу зашел анон, { $modx->sendRedirect($modx->makeUrl(14));//отправляем его на страницу номер 14, именно там у нас вход в систему. exit;//на случай если редирект ВДРУГ не сработал, завершаем скрипт наверняка(вместо странички будет просто белый экран) }
Пытаемся загрузить запрашиваемый документ(ресурс, где используется этот сниппет имеет вид /docs?doc_id=N).
$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);//пишем в лог } }
Возвращаем в плейсхолдере [[+doc]] контент ресурса, состоящий из документа и форм для его редактирования, к которым есть у юзера доступ.
/**********Вывод данных в форму*************/ 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 ''; }
Для лучшего понимания кому-то может быть удобнее посмотреть код в один блок:
Выполняющийся код сниппета 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 ''; } return 'Ошибка...';//для нормального куда мы здесь вообще не должны оказаться, так что вернем хоть что-то для объяснения неполадки
3. Форматирование полученных данных и вывод на страницу
3.1 Необходимые чанки
Для правильного вывода документа используются три простых чанка:
Rights — форма для редактирования прав
<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>
DocTitle — шаблон отображаемого заголовка документа.
<h2>[[!+title]]</h2>
DoctText* — шаблон текста самого документа
[[!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> <script type="text/javascript"> CKEDITOR.replace( 'text' ); CKEDITOR.config.height=800; </script> <!--это строка только для CKeditor--> [[!If? &subject=`[[!+CanEdit]]` &operator=`EQ` &operand=`true` &then=`<input type="hidden" name="edit" value="1"/> <input type="submit" value="Сохранить новый текст договора..."/> </form>`]]
* — если почему-то форма не выводится никогда, стоит проверить, а установлен ли компонент If?
И простейший сниппет для получения параметра из адресной строки:
GET
<? return isset($param)?$_GET[$param.""]:'0';
Итоговый код сниппета 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-редактор, я, например, использовал CKeditor.
CKeditor ставится так
Добавляете в шаблон такой заголовок в <head>*
* — для этого я определил TV ExtraHeaders, добавил его в <head> в шаблоне, и там, гле нужны были доп заголовки ресурсу, задавал значение этого TV. Можете это добавить прямо в чанк DocText перед textarea, но не советую так делать(надо разделять код и данные).
<script src="//cdn.ckeditor.com/4.4.5/full/ckeditor.js"></script>
* — для этого я определил TV ExtraHeaders, добавил его в <head> в шаблоне, и там, гле нужны были доп заголовки ресурсу, задавал значение этого TV. Можете это добавить прямо в чанк DocText перед textarea, но не советую так делать(надо разделять код и данные).
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);//готово, мы создали документ ...
В этой статье я старался показать, как идея переходит в логику, далее в код и в итоге — в работающий механизм.
Надеюсь, кому-то это будет полезно.
Заранее извиняюсь за ошибки в тексте статьи (хоть я и старался все отловить). Опыта написания больших текстов у меня нет. Буду рад любым замечаниям.
