Szegedi Tudományegyetem Informatikai Tanszékcsoport
Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Szakdolgozat
Készítette:
Külső témavezető:
Biczó Dezső
Csécsy László
gazdaságinformatikus szakos hallgató
főfejlesztő, KYbest
Belső konzulens:
Havasi Ferenc egyetemi tanársegéd
Szeged 2013
FELADATKIÍRÁS Egy éppen készülő Drupal 7-es ügykezelőrendszer bizonyos funkcióinak önálló elkészítése. Ezek közé a funkciók közé tartozik a rendszerhez tartozó jogosultság- és értesítési rendszer megvalósítása, valamint egy minden igényt kiegészítő keresés funkció implementálása.
2
TARTALMI ÖSSZEFOGLALÓ
A téma megnevezése: Ügykezelőrendszer elkészítése Drupal alapokon.
A megadott feladat megfogalmazása: Egy cég előző Drupal 6 alapú ügykezelőrendszerének kiváltására szolgáló Drupal 7-es ügykezelőrendszer elkészítése. Az új rendszernek ötvöznie kell az előző rendszer összes előnyét a cég összes új elvárásával, illetve lehetőség szerint ki kell küszöbölnie az előző rendszer ismert hiányosságait, hátrányait.
A megoldási mód: A különböző funkciók megoldásához sok különböző Drupal modult használtam fel, amelyeknek néhány hiányosságát saját megoldásokkal, javításokkal kellett pótolnom. Azokra a funkciókra, amelyekre nem volt már létező vagy jól működő megoldás saját megoldást alkalmaztam.
Alkalmazott eszközök, módszerek: A fejlesztés során szerver oldalon Apache webszervert használtam PHP szerver oldali szkriptnyelvvel és MySQL adatbázissal, valamint GIT verziókezelő rendszert a forráskód verziókezelésére. A fejlesztés a saját gépemen történt Windows vagy Linux operációs rendszeren. Windows operációs rendszeren a Git 1.8.3 –as verzióját és az XAMPP 1.8.2-es szervercsomagot használtam, amely Apache 2.4.4, PHP 5.4.19, Xdebug v2.2.3 és MySQL 5.5.32 komponenseket tartalmazott. Ubuntu 12.04 operációs rendszeren a Webmin 1.5.80-as webes adminisztrációs interfészt a LAMP szoftvercsomaggal használtam, amely Apache 2.2.22, PHP 5.3.10, Xdebug v2.1.0 és MySQL 5.5.34 komponenseket tartalmazott, mellé pedig a GIT 1.7.9.5 verziót telepítettem.
Elért eredmények: A cég számára sikerült kifejleszteni az új ügykezelőrendszert, amelyet azóta használatba is vettek. A fejlesztése tovább folyik a fejlesztőcsapat visszajelzései alapján, újabb ötletekkel és javításokkal bővül a rendszer azzal a nem titkolt céllal, hogy egyszer termékként is a nagy közösség elé lehessen tárni az ügykezelőrendszert.
Kulcsszavak: PHP, Drupal, ügykezelőrendszer, webfejlesztés, kereső, Search API, Organic Groups, Notifications, GIT, opensource, CMS 3
TARTALOMJEGYZÉK FELADATKIÍRÁS ..................................................................................................................................................... 2 TARTALMI ÖSSZEFOGLALÓ ....................................................................................................................................... 3 TARTALOMJEGYZÉK................................................................................................................................................ 4
BEVEZETÉS .............................................................................................................................................. 6 1.
HÁTTÉRISMERETEK ....................................................................................................................... 9
1.1.
A WEBES FEJLESZTŐKÖRNYEZET ................................................................................................................... 9
1.1.1.
Telepítés Linuxon .................................................................................................................................. 9
1.1.2.
Telepítés Windowson ......................................................................................................................... 12
1.2.
A TARTALOMKEZELŐ RENDSZEREK (CMS) ................................................................................................... 13
1.2.1. 1.3.
A Drupal .............................................................................................................................................. 14 A VERZIÓKEZELŐ RENDSZEREK (VCS).......................................................................................................... 15
1.3.1.
A GIT ................................................................................................................................................... 15
1.4.
ÜGYKEZELŐ RENDSZEREK ......................................................................................................................... 16
1.5.
A FELELŐS FEJLESZTŐ FOGALMA................................................................................................................. 17
2.
FEJLESZTÉS DRUPALBAN .............................................................................................................19
2.1.
A Drupal Hooks API ........................................................................................................................... 19
2.2.
A modulok felépítése ........................................................................................................................ 21
2.3.
A telepítési profilok .......................................................................................................................... 23
3.
AZ OPENSPACE FEJLESZTÉSE .......................................................................................................26
3.1.
Így fejlesztünk mi! ............................................................................................................................. 26
3.2.
Hozzáadott értékeim a rendszerhez .................................................................................................. 28
3.2.1.
A telepítő profil további bővítése ....................................................................................................... 28
3.2.2.
A jogosultságrendszer megvalósítása ................................................................................................. 30
3.2.3.
Az értesítési rendszer.......................................................................................................................... 37
3.2.4.
A kereső funkció ................................................................................................................................. 49
4.
ÖSSZEFOGLALÁS ...........................................................................................................................68
4.1.
AZ ELÉRT EREDMÉNYEK ........................................................................................................................... 68
4.2.
BŐVÍTÉSI LEHETŐSÉGEK ........................................................................................................................... 69
IRODALOMJEGYZÉK ...............................................................................................................................70 NYILATKOZAT ..................................................................................................................................................... 74 KÖSZÖNETNYILVÁNÍTÁS ........................................................................................................................................ 75
MELLÉKLETEK ......................................................................................................................................76 1.1.
IQ_PROFILE_INSTALL_EXTRA_FEATURES() ................................................................................................... 76
1.2.
OG NODE - PROJECT PERMISSIONS TÁBLÁZAT .............................................................................................. 78
1.3.
PERMISSION MAZE ................................................................................................................................. 81
4
1.4.
PERMISSION AXES .................................................................................................................................. 90
1.5.
OG NOTIFICATIONS PATCH ...................................................................................................................... 91
1.6.
IQ_NOTIFICATIONS.CLASSES.INC ................................................................................................................ 93
1.7.
_IQ_NOTIFICATIONS_GENERATE_TEASER() .................................................................................................. 99
1.8.
SEARCH API MULTI INDEX FACETS RÖVID TÖRTÉNETE .................................................................................. 100
1.9.
OPENSPACE_SEARCH_HACK.MODULE ELSŐ VERZIÓ RÉSZLET ........................................................................... 101
1.10.
HANDLER_FILTER_FULLTEXT.INC .............................................................................................................. 105
1.11.
_IQ_SEARCH_PREPROCESS_SEARCH_API_RESULT() ..................................................................................... 107
1.12.
IQ_SEARCH_VIEWS_POST_RENDER() ....................................................................................................... 108
1.13.
FAKE_VIEWS_EXPOSED_SEARCH_MULTI_FORM() ....................................................................................... 109
1.14.
_IQ_SEARCH_GENERATE_OPTIONS() ........................................................................................................ 111
1.15.
FAKE_EXPOSED_FILTER.JS ...................................................................................................................... 112
1.16.
IQ_SEARCH_FORM_VIEWS_EXPOSED_FORM_ALTER()
1.17.
COMMENT ACCESS CHECK PATCH A SEARCH API-HOZ .................................................................................. 114
................................................................................. 113
5
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
BEVEZETÉS Szakdolgozatom témája egy ügykezelőrendszer megvalósítása, amely a Drupal ingyenes, nyílt forráskódú tartalomkezelőjére épül. A témát egy cég szolgáltatta, ahol az új ügykezelőrendszer fejlesztésén többedmagammal veszek részt. A projekt fejlesztése Openspace (jelenleg már IssueQ) néven folyik, amelyben voltak olyan feladatok, amelynek megoldása
rám
hárult.
Ezek megvalósításának menetéről szól a
szakdolgozatom. A motiváció az új ügykezelőrendszer elkészítése mögött legfőképpen a cég régi Drupal 6-os ügykezelőrendszerének kiváltása volt, amely szintén saját fejlesztés volt. Elkészülte óta a technológia sokat fejlődött, megjelent a Drupal 7-es verziója és eljött az a pillanat, amikor jobbnak tűnt a jelenlegi rendszer hibáinak foltozása és hiányosságainak pótlása helyett egy új rendszer megtervezése és megvalósítása. Természetesen az Openspace fejlesztését megelőzően és a saját régi ügykezelőrendszer mellett már volt a cégnek tapasztalata más, népszerű, mások által kedvelt egyéb ügykezelőrendszerekkel is, így tisztában voltunk azok előnyeivel, hátrányaival és hiányosságaival is. A tervezés és fejlesztés során igyekeztünk az ezekből szerzett tapasztalatokat is kamatoztatni. A teljesség igényeit ki nem elégítve íme pár ezek közül: Redmine, Asana, Basecamp, Jira, Open Atrium, stb . Mik voltak a legfőbb pontok az új ügykezelőrendszer alapjainak lefektetése során? Az ügykezelőrendszer a cég projektjeinek menedzselésére szolgál, amelyekből lehet akár több száz is a rendszerben. A cégnél távmunkában dolgozik több fejlesztő is, akik különböző projekteken dolgoznak, néha új projektekbe vonják be őket, néha régieknek válnak részeseivé. Ezen kívül vannak olyan projektek, ahol a megrendelőnek is szükséges lenne hozzáférést biztosítani a saját projektjeihez, hogy láthassa a fejlesztés haladását, válaszolhasson az esetlegesen felmerülő kérdésekre. Mindemellett mégis jó lenne, ha a megrendelők a projektjeiken belül is csak azokhoz a információkhoz (tartalmakhoz) férhetnének hozzá, amelyek mindenképp szükségesek számukra. (Például egy projekttel kapcsolatos fejlesztési dokumentáció, amely a szerver eléréseket 6
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
tartalmazza nem feltétlen részese ennek.) Erre a jelenlegi ügykezelőben nem volt lehetőség. Ennek megvalósítása egy nagyon komplex és bonyolult jogosultságrendszer megvalósítását vonja maga után, amelyhez Organic Groups (OG) nevű modult használtam fel. Mivel az idő elteltével egy fejlesztő szintén akár 100-as nagyságrendű projektnek is részese lehet (és ezeknek a nyomon követésére nem mindig elég egy egyszerű web böngészőben megjeleníthető lista), ezért szükség lenne egy értesítési rendszerre is, amely (egyelőre) emailen keresztül értesítési a fejlesztőt, ha új ügyet kap vagy új hozzászólás érkezett valamely ügyéhez. Ezen kívül még fel tud iratkozni ügyekre és projektekre, amelyekről szeretne értesítést kapni. Mindezt a régi rendszer is tudta megközelítőleg, azonban szükséges lenne az új rendszerben olyan kiegészítő funkciókra is, mint: -
Az
új
ügykezelőrendszer
részét
képező
projektekhez
kapcsolódó
új
tartalomtípusokra való automatikus feliratkoztatás és értesítés. Ilyen például az új Dokumentum tartalomtípus, amely a projekthez kapcsolódó eléréseket, specifikációkat, stb. tartalmazhatja. Ezek létrejöttéről, módosulásairól minden projekthez tartozó fejlesztőnek értesítést kell kapnia. A projektekhez kapcsolódó Hír
tartalomtípusra
pedig
minden
felhasználót
fel
kell
iratkoztatni
automatikusan. -
Adott projektre való feliratkozáskor a hozzá kapcsolódó összes tartalomtípusra (Ügy, Dokumentum, stb.) legyen feliratkoztatva a felhasználó.
-
Az új rendszer része lesz egy címkéző rendszer is, amellyel minden tartalomhoz lehet úgynevezett tageket megadni, ezzel is megkönnyítve a tartalmak közötti navigációt. Ezekhez kapcsolódóan feladat volt, hogy legyen lehetőség a különböző címkékre is feliratkozni. Így lehetősége lesz egy felhasználónak például csak az „update” címkével ellátott tartalmakról értesítést kapni.
Erre a célra a régi és az új rendszerben is a Notifications modul lett felhasználva, amelynek van rengeteg hátránya is, de adott pillanatban még nem volt ennél jobbnak tűnő megoldási lehetőség előttünk. Mivel az előző rendszernek is része volt ez a modul, ezért ismert volt azon hibája, miszerint az egymást fedő feliratkozási lehetőségek miatt bizonyos esetekben képes volt egy eseményről 2 vagy akár több email értesítést is kiküldeni. Ezzel a hibával azonban az értesítési rendszer előnyeit hamar nem kívánt levélszemét áradattá változtatta, így ennek megszüntetése is fontos feladat volt az új 7
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
rendszerben. Végeredményül sikerült minden elvárást kielégítenem ezzel a funkcióval is. Az előzőekben már szó esett arról, hogy a rendszer huzamosabb használata során mennyi tartalom halmozódhat fel az ügykezelőben, amelyekben igen hasznos lenne alkalomadtán keresni is. Erre a régi rendszerben egy végtelen egyszerű beépített Drupal kereső állt csak rendelkezésre, amely egyszerűségénél fogva szinte használhatatlannak minősült, a saját célját nem tudta megfelelően betölteni. Mivel az új rendszer jóval összetettebb lesz, ezért szükséges lenne egy olyan keresőt is implementálni bele, amely a szöveges keresőn felül még különböző hasznos mezőkre (pl.: szerző, tartalomtípus, projekt) is képes szűrni a keresési eredményeket. Természetesen minden felhasználónak csak azon tartalmakat lenne szabad eredményül látnia, amelyekhez van jogosultsága és a keresés megfelelő sebessége is egy lényeges tényező lenne. Ennek elkészítésére a Search API és FacetAPI modulok kombinációja tűnt a legjobb próbálkozásnak, de mint kiderült a kitűzött feladat jóval bonyolultabb volt Drupalban, mint ahogy azt elsőre gondoltuk volna. A feladat mégis végül sikeresen megvalósult.
8
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1. HÁTTÉRISMERETEK 1.1.
A WEBES FEJLESZTŐKÖRNYEZET
A webes projektek fejlesztésének nélkülözhetetlen kezdőlépése egy olyan stabil, gyors és jól használható lokális fejlesztőkörnyezet létrehozása, amelyben az általunk létrehozott alkalmazásokat hatékonyan tudjuk fejleszteni, tesztelni és debuggolni. Ennek fő mérföldköve a webszerver telepítése és megfelelő konfigurálása. Mivel a Drupal egy igen grandiózus méretű rendszerré nőtte ki magát az évek során, ezért vannak olyan szükséges „trükkök” (beállítások), amelyeket alkalmazva elérhetjük, hogy fejlesztőkörnyezetünkön hatékonyabban és megfelelőbben fusson a rendszer. Ennek az alapjait mutatom be a következőekben. Lesznek olyan beállítások, amelyeket azonosan el kell végezni Windowson és Linuxon, ezekre majd külön felhívom a figyelmet. 1.1.1. Telepítés Linuxon A fejlesztőkörnyzet alapjai végtelen egyszerűen telepíthetőek Linuxon (az általam használt Ubuntu 12.04-en) az alábbi lépésekkel:
A tasksel csomagkezelő telepítése: sudo apt-get install tasksel
Ezután a tasksel csomagkezelővel telepítsük fel az lamp-server csomagot, amely tartalmazza az alap web szervert, amire szükségünk lesz. sudo tasksel install lamp-server
Ezeken kívül még szükségünk lesz az alábbi csomagok telepítésére is: sudo
apt-get
install
php5-curl
php5-gd
php5-dev
php5-mcrypt
php5-mysql phpmyadmin php5-cli drush
Ezek közül kiemelném a drush –t, amely egy olyan parancssori eszköz, amely lehetővé teszi különböző feladatok elvégzését parancssoron keresztül a telepített Drupalunkon belül. Ilyen például a modulok letöltése, telepítése és törlése. A phpMyAdmin pedig a MySQL adatbázisban tárolt adatok listázását, szerkesztését, törlését, importálását, stb teszi számunkra egyszerűen lehetővé.
Ha
ezekkel
megvagyunk,
http://localhost
akkor
nyissuk
meg
böngészőnkben
a
urlt, ahol ha sikerrel jártunk akkor az alábbi felírat fog minket
fogadni az oldalon: It works!
Ezek után már nincs más dolgunk, mint pár hasznos beállítást elvégezni a rendszeren, mielőtt elkezdenénk rajta a Drupal fejlesztést: 9
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
o Vegyük saját tulajdonunkba a /var/www mappát, ahonnan a webszerver alapértelmezetten kiszolgálja az oldalakat. Így hozzá tudunk majd férni a mappához és gond nélkül tudunk telepíteni oda Drupalt. sudo chown felhasználónév:felhasználónév –R /var/www
o Futtassuk az Apache web szervert saját felhasználónkként, így szerver által letöltött fájlokhoz is lesz jogosultságunk hozzáférni. Nyissuk meg a sudo nano /etc/apache2/envvars
és írjuk át az alábbi sorokat: export APACHE_RUN_USER=felhasználónév export APACHE_RUN_GROUP=felhasználónév
majd indítsuk újra a web szervert az alábbi paranccsal: sudo service apache2 restart
o Tuningoljuk a PHP alapbeállításait, hogy a Drupal stabilabban fusson a szerverünkön. Cseréljük ki a /etc/php5/apache2/php.ini fájlban az alábbi változók értékét az itt feltűntetettekre: memory_limit=128M max_execution_time=600 max_input_time=300 realpath_cache_size=64k post_max_size=32M upload_max_filesize=32M
o Módosítsuk a MYSQL [38] szerver alapbeállításait is hasonló céllal. Adjuk hozzá az alábbi sorokat illetve cseréljük ki a meglévő változók értékét az alábbiakra a /etc/mysql/my.cnf fájlban1: key_buffer = 1024M max_allowed_packet = 64M thread_cache_size = 286 table_open_cache = 512 join_buffer_size = 4M read_buffer_size = 8M sort_buffer_size = 16M read_rnd_buffer_size = 8M myisam_sort_buffer_size = 128M thread_cache_size = 286 net_buffer_length = 64K tmp_table_size = 64M 1
Ezek a beállítások egy 8GB RAM-mal rendelkező gépre vannak optimalizálva.
10
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
max_heap_table_size = 64M # Try number of CPU's*2 for thread_concurrency thread_concurrency = 8
innodb_log_file_size = 384M innodb_log_group_home_dir = /var/lib/mysql innodb_data_home_dir = /var/lib/mysql innodb_data_file_path = ibdata1:50M:autoextend innodb_log_files_in_group = 2 innodb_flush_method = O_DIRECT innodb_log_buffer_size = 16M innodb_thread_concurrency = 4 # If you have more than 512MB ram, you can increase the innodb_buffer_pool_size innodb_buffer_pool_size = 1536M innodb_additional_mem_pool_size = 20M innodb_file_per_table # If you have raid5 or raid10 with more disks, you can increase innodb_file_io_threads innodb_file_io_threads = 1 innodb_flush_log_at_trx_commit = 2 innodb_max_dirty_pages_pct = 70
o Telepítsük
a
debuggoláshoz
jóformán
nélkülözhetetlen
xDebug
kiegészítőt. sudo apt-get install php5-xdebug
Majd a /etc/php5/conf.d/xdebug.ini fájlhoz adjuk hozzá az alábbi sort: xdebug.remote_enable = 1
Majd ismét indítsuk újra a web szervert: sudo service apache2 restart
Ezután már kedvenc IDE-nkkel és kedvenc böngészőnk megfelelő beállításával használatba is tudjuk venni az xDebug által nyújtott hibakeresési lehetőségeket. Ezek lennének egy valóban egyszerű, mégis jól használható Drupal fejlesztésre alkalmas web szerver alap beállításai Ubuntu operációs rendszeren. Ezen kívül persze még rengeteg hasznos kényelmi beállítást tehet az ember, amelyeket nem részleteznék most itt. Számomra ilyen például a dnsmasq csomag és apache vhost beállítása, amellyel megoldható egy saját .loc domain bevezetése, ami után a saját fejlesztői
11
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
oldalainkat
a
fentebbi
domain
címmel
érthetjük
el
a
böngészőből
(pl.:
http://szakdolgozatom.loc). Valamint személy szerint az én kedvenc kiegészítőm még a Webmin és a Virtualmin, amely egy teljes körű webes adminisztrációs felületet telepít a szerverünkre. Ez különösen hasznosak abban az esetben, ha a szerver egy távoli- vagy virtuális gépen fut, amely csak konzolról elérhető. Ennek beállítása is viszonylag összetett és meghaladja e szakdolgozat kereteit. 1.1.2. Telepítés Windowson A Windowsos web szerver telepítése és kalibrálása a fentiekhez képest mondhatni jóval egyszerűbb, a legtöbb lépése elvégezhető a mezei felhasználóktól megszokott nextnext-next mozdulatsorral.
Töltsük le az http://xampp.org-ról a Windows telepítőt. (Lehetőség van portable verzió letöltésére, ha azt jobbnak tartjuk, dolgozzunk azzal.)
Telepítsük az XAMPP szervercsomagot a next-next-next metódust alkalmazva. Ezzel a feltelepített XAMPP alapbeállításként a c:\xampp path-ra települ rendszerünkben. Ekkor a web szerver gyökér könyvtára a c:\xampp\htdocs lesz.
Keressük meg a Start menüben a Programok közt az XAMPP Control Panel alkalmazását, majd indítsuk el. Itt lesz lehetőségünkre az Apache és a MYSQL szerver elindítására (és újraindítására is majd). Miután ezt megtettük nyissuk meg szokás szerint a http://localhost urlt böngészőnkben. Itt az XAMPP saját kezdőoldala kell, hogy fogadjon minket.
Ezek után végezzük el a Linuxos telepítésnél is alkalmazott plusz beállítások egy részét: o Tuningoljuk a PHP-t. A konfigurációs fájl a c:\xampp\php\php.ini elérési útvonalon található. A javasolt beállítások ugyanazok, mint a fentebb már kifejtett Linuxosak. o Állítsuk
be
a
MYSQL
c:\xampp\mysql\bin\my.ini
szervert
is.
A
konfigurációs
fájl
a
elérési útvonalon található. A javasolt
beállítások szintén hasonlóak a Linux telepítésnél említettekhez, egyedül az alábbi elérési útvonalak átírása szükséges azokhoz képest: innodb_log_group_home_dir = c:/xampp/mysql/data innodb_data_home_dir = c:/xampp/mysql/
o Az xDebug külön telepítésére nincs szükség, az XAMPP csomag alapból tartalmazza, már csak be kell kapcsolnunk. Bekapcsolására szintén a 12
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
c:\xampp\php\php.ini
fájlban van lehetőség, a fájl végén kommentezzük
ki az alábbi sorokat:
Drush is
zend_extension = "c:\xampp\php\ext\php_xdebug.dll" xdebug.remote_enable = 1 xdebug.remote_handler = "dbgp" xdebug.remote_host = "127.0.0.1" xdebug.remote_port = 9000 telepíthető Windows operációs rendszerre. Ehhez a http://drush.ws
oldalról kell letöltenünk a telepítőt. Szintén egyszerűen telepíthető a next-nextnext metódussal. Egyedül egy beállítást javaslok megtenni, mégpedig a telepítőben válasszuk ki a „Register Environment Variables” opciót, ugyanis ez esetben a drush parancs bárhol használható lesz a rendszerben a parancssorból. 1.2.
A TARTALOMKEZELŐ RENDSZEREK (CMS)
A tartalomkezelő rendszerek (angolul: Content Management System, rövidítve: CMS) olyan (többnyire webes) szoftverek halmaza, amelyek lehetővé teszik a tartalmak távolról történő létrehozását, szerkesztését, akár több személy együttműködésével is. A tartalomkezelő rendszerek feladata, hogy olyan felhasználói interfészt biztosítsanak a felhasználóknak,
amelynek köszönhetően nem
kell
rendelkezniük
semmilyen
programozási nyelv ismeretével, ahhoz, hogy közzétehessék mondanivalóját (pl.: az interneten). A CMS-ek alap funkciói közé tartozik a hozzáférések felhasználói szerepkörök szerinti szabályozása, a könnyű adattárolási és adatelérési lehetőségek biztosítása, a redundáns tartalmak bevitelének csökkentése, vagy akár a felvitt tartalmak közti egyszerű, gyorsított keresés lehetősége. [39,50,59] A tartalomkezelő rendszereknek több fajtája ismert:
Vállalati szintű tartalomkezelő rendszer - Enterprise Content Management Systems (ECMS)
Webtartalom-kezelő rendszer - Web Content Management System (WCMS)
Dokumentumkezelő rendszer - Document Management System – (DMS)
Mobil tartalomkezelő rendszer - Mobile Content Management System (MCMS)
Komponens tartalomkezelő rendszer - Component Content Management System (CCMS)
Digitális vagyonkezelő rendszer - Digital Asset Management (DAM)
13
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Oktatási tartalomkezelő rendszerek - Learning Content Management System (LMS)
A CMS-ek a webes alkalmazásfejlesztő keretrendszerek (WAF - Web Application Framework) [51] egy specializált változatai, amelyek bővebb alapfunkcionalitást nyújtanak
a
fejlesztők
számára
az
egyszerű
webes
alkalmazásfejlesztő
keretrendszerekhez képest. Ilyen bővítés lehet például a saját API vagy a saját coding standard is. 1.2.1. A Drupal A Drupal a fentebb említett CMS kategóriák közül a WCMS-be tartozik, tehát webes tartalomkezelést tesz lehetővé. [52, 53] Olyan ingyenes, PHP alapú, nyílt forráskódú szoftver, amely önmagában több mint egy egyszerű tartalomkezelő rendszer. Hihetetlenül összetartató fejlesztői közösség áll mögötte az egész világon, amelynek köszönhetően folyamatosan fejlődik, bővül, mégis megőrzi karbantarthatóságát. Legfőbb értékei közé tartozik még, hogy igyekszik a legújabb technológiákat magába ötvözni, mindemellett mégis visszafelé kompatibilisnek maradni és rugalmasnak lenni. Moduláris felépítésének köszönhetően a bővítési lehetőségei határtalannak mondhatóak. A Drupal két fő részből áll: a Drupal Core-ból, amely magát az alaprendszert tartalmazza
pár
szükségeses
alapmodullal,
amelyek
szükségesek
lehetnek
a
tartalomkezelő rendszerként való használathoz. Ezt a funkcionalitást lehet tovább bővíteni a már-már megszámlálhatatlan mennyiségűnek mondható, a közösség által fejlesztett modullal (Drupal Contributions). Mivel a Drupal tartalmaz egy Minimal telepítési profilt, amely valóban csak egy teljes egészében lecsupaszított Drupal Core-t telepít számunkra, ezért méltán nevezhető és használható akár webes alkalmazásfejlesztő keretrendszerként is. A Drupal a GNU General Public Licence (röviden: GPL) [56] licenc alatt jelenik meg, amely magában foglalja, hogy a szoftver szabadon terjeszthető (akár pénzért is), szabadon módosítható, de az új terjesztéseknek és a módosított
14
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
szoftvereknek is a GPL licenc alatt kell megjelenni. Ezzel biztosítja, hogy minden szabad forrásból származó szoftver a jövőben is szabad szoftver maradjon. 1.3.
A VERZIÓKEZELŐ RENDSZEREK (VCS)
A verziókezelő rendszerek (angolul: Version Control System, röviden: VCS) az adatok (forráskódok, fájlok, stb) több verzióban létező változatainak kezelését teszik egyszerűen lehetővé. A rendszerben tárolt adatok bármely tetszőleges verziója (változata) bármikor visszaállítható, illetve tetszőleges verziók közötti különbség bármikor kinyerhető. Fontos szerepük van a szoftverfejlesztésben, ahol több ember dolgozik egyszerre ugyanazon a projekten, és munkájuk gyümölcse az egyéni fejlesztők munkájának összességéből fog összeállni. Megkülönböztetünk köztük elosztott verziókezelő rendszereket és központosított verziókezelő rendszereket. A központosított verziókezelő rendszereket a kliens-szerver modellt alkalmazva a különböző verziókról tárolt információt egy központi szerveren tárolják, a verziók kezelése ott történik. Az elosztott verziókezelő rendszerek ezzel szemben a decentralizáltan működnek, minden felhasználó gépe egy külön adattárolóként működik. A szinkronizáció az egyes gépek között küldött patch-ekkel (módosításcsomagokkal)
történik.
Ez
a
megközelítés
jelentősen
gyorsabb
a
központosítotthoz képest és jóval nagyobb adatvédelmet biztosít, hiszen minden felhasználó gépe egy-egy külön (backup) tároló. [60] Pár példa verziókezelő rendszerekre [48]:
Nyílt forrású, központosított verziókezelők: Subversion (SVN), CVS / CVSup, OpenCM, stb
Nyílt forrású, elosztott verziókezelők: GIT, darcs, Aegis, stb.
Zárt forrású, központosított: Perforce, Microsoft SourceSafe, IBM CMVC, stb
Zárt forrású, elosztott: Bitkeeper, stb.
1.3.1. A GIT A GIT egy nyílt forrású, elosztott teljes értékű verziókezelő rendszer. Fejlesztése Linus Torvalds nevéhez fűződik, akit a Bitkeeper és a Monotone verziókezelő rendszer ihletett meg és a Linux kernel fejlesztéséhez készítette el a GIT-et. A GIT többségében C 15
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
nyelven írt programok halmaza, számos shell, Perl és egyéb szkripttel kibővítve, amely újraírása után elérhető lett Windows operációs rendszereken is. A GIT fő jellemzője, hogy minden GIT munkaállomás egy teljes értékű repository (tároló), teljes verziótörténettel és teljes revíziókövetési rendszerrel, amely nem függ semmilyen központi szervertől, így bármilyen hálózat elérhetőségétől sem. Támogatja a nem-lineáris fejlesztését, amelynek köszönhetően a különböző branchekben tárolhatjuk kódbázisunk egy adott állapotát. A repositoryk többféle módon (HTTP, FTP, SSH, rsync, stb) megoszthatóak a hálózaton, a nagy fájlok verziókövetése is gyorsan megoldható. A git-svn híd segítségével a Subversion svn repositoryk is közvetlenül használhatóak. [54, 55] 1.4.
ÜGYKEZELŐ RENDSZEREK
Ezt a kifejezést leginkább az angol Issue Tracking System (ITS) vagy a Bug Tracking System kifejezéssel lehetne párhozamba hozni, de szoktuk egyszerűen Casetracker-nek is nevezni. Ezeknek a szoftvereknek leginkább a cégek ügyfélszolgálatain vagy fejlesztőcsapatoknál van nagy jelentőségük. Segítenek menedzselni egy adott cég különböző projektjeivel kapcsolatos visszajelzéseket, feladatokat. Fontos támpontjai egy jól működő cég „napi könyvelésének”, melynek köszönhetően nyomon követhetőek személyre lebontva az elvégzett- és az elvégzendő feladatok is akár különböző időintervallumokra lebontva. Ezek a szoftver rendszerek általában hálózatokon üzemelnek (pl.: LAN, Internet, stb.), az adatokat egy központi szerveren tárolják, ezzel is megkönnyítve a felhasználóknak, hogy helytől függetlenül, bármely munkaállomáson hozzáférhessenek a rendszerben tárolt információkhoz, lekérdezzék vagy módosítsák azt. Az ügykezelőrendszerek ugyancsak fontos alappillérei a különböző szerepkörök és hozzájuk kapcsolódó jogosultságszabályozások. Ezek adott cégre jellemző workflowként különbözőek lehetnek, de általánosságban elmondható, hogy biztos vannak adminisztrátori és felhasználói szerepkörök a rendszerben, akik aztán tovább bonthatóak például projektmenedzser-, fejlesztő-, tesztelő-, vagy akár megrendelő szerepkörre is. [49,57]
16
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Pár bugtracker és issue tracker szoftver a teljesség igényét ki nem elégítve: Bugzilla, MantisBT, JIRA, Github, stb. [57] 1.5.
A FELELŐS FEJLESZTŐ FOGALMA
A fenti kifejezés (angolul: responsible maintainer) Drupalos körökben ismert és elterjedt, hiszen maga a Drupal atyja Dries Buytaert is írt róla blogjában [42]. Mit jelent ez? Mivel a Drupal egy nyílt forrású projekt, melyet egy közösség fejleszt, ezért fontos, hogy minden egyes fejlesztő, aki szerepet vállal benne, felellőséget vállaljon a saját munkája után. Ez legfőképpen ez a Drupal modulok területén okoz problémát, mivel a Drupal Core-t egy jól összeválogatott csapat tartja karban. Tegyük fel, hogy adott XY fejlesztő, aki elkészít egy modult (nevezzük Demonak), és publikálja a közösség számára a drupal.org-on. Mivel XY tudja, hogy még nincs kész a munkája, ezért developement (dev) verzióként teszi azt közzé, ezzel felhívja a figyelmét a leendő használóknak, hogy a Demo modul használata veszélyeket rejthet magában. (Nem ajánlott éles oldalon a felhasználni.) A felhasználók ezt tudomásul veszik, szabadon döntenek úgy, hogy használják, majd ha eközben hibát találnak, akkor azt jelzik a modul issuetracker-ében drupal.org-on. Mi lehet ebben a probléma? Mivel a Drupal igyekszik karbantarthatónak és biztonságosnak maradni, ezért létezik egy Drupal Security Team, aki a beérkezett Security Issue besorolású ügyekkel törődik. Ez azt jelenti, hogy az ilyen besorolású hibajelentéseket nem teszik közzé sehol sem addig, amíg javításuk el nem készül. A hibáról beérkezése után értesítik privát csatornán a modul fejlesztőjét, akinek lehetősége van kijavítani azt, vagy a Security team maga törődik a problémával. (Esetleg a hibajelentés mellé egyben kaptak javító patchet is, amelyet le fognak tesztelni.) Tehát összességében elmondható, hogy a sebezhetőség biztos javítva lesz, és a hiba csak a javítása után kerül napvilágra. Mivel azonban több ezer modulról beszélünk már jelenleg is, ezért nem törődhetnek mindennel. Így a nem stable release-nek jelölt kiadásokra (pl.: dev, rc1…rcN, alpha1…alphaN, beta1…betaN, stb.) beérkező bugreportok úgymond mellőzve lesznek. Ezek a hibajelentések csak a modul issuetracker-ében lesznek jelen, ahol bárki
17
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
tudomást szerezhet a sebezhetőségről és kihasználhatja azt. Sőt mi több, a hiba kijavítására sincs semmilyen garancia. Mi lenne a helyes eljárás? A helyes eljárás az, hogy én, mint nyíl forrással dolgozó fejlesztő felelősséget vállalok a saját kódomért, és kitűzök egy (nem túl távoli) dátumot, amikor a folyamatos fejlesztésnek köszönhetően azt mondom, hogy a modulom kész! A modulom kész és felelősséget vállalok érte, ha valami hibás benne, akkor vállalom, hogy kijavítom. Ezt úgy teszem meg, hogy kiadok egy stable release-t a modulomból. Ezután már a modulommal kapcsolatos Security Issue-kkal a Drupal Security Team is törődni fog. [43] Mindezekhez hozzátenném még, hogy mint felelős fejlesztő az általam készített modult nem hagyom magára, támogatást nyújtok hozzá, vagy kijelölök egy új maintainert, aki helyettem, mint készítő helyett is hasonló minőségben tudja tovább folytatni a modullal kapcsolatos feladatokat (javítások, támogatás, stb.). Sajnos a szakdolgozat kapcsán is találkoztam jó pár olyan Drupal modullal, amelyeknél ezek a követelmények nem teljesültek. Nevén nevezve a legtöbb problémát talán a Notifications és a hozzá kapcsolódó OG Notifications modulok adták, amelyekből csak alpha2 illetve dev verziók vannak jelen, pedig nem mostanában jelentek meg. A kódjuk rosszul karbantartott, támogatás nincs hozzájuk, ha problémába ütközik a fejlesztő, akkor úgymond „Csináld magad!” módon kell megoldania.
18
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
2. FEJLESZTÉS DRUPALBAN 2.1.
A Drupal Hooks API
A hookok a Drupal rendszer egyik fontos alapkövei. Egyetlen backend Drupal fejlesztő sem nélkülözheti a hookok ismeretét tudásbázisából hosszú távon. A hookok úgynevezett eseménykezelők (triggerek) Drupalban, amelyek adott esemény hatására futnak le az oldalak generálása közben. Futásukkor képesek adatokat módosítani vagy elvégezhetnek újabb feladatokat, amelyek adott feltétel hatására hajtódnak végre. (Például: a legtöbbet használt Drupal hookok node betöltésekor vagy egy felhasználó bejelentkezésekor futnak le.) Ha objektumorientált programozás (OOP – Objektum Oriented Programing) szempontjából nézzük a Drupal hookjait, akkor a metódussokkal tudjuk őket rokonságba hozni.
Ellenben
azonban
az
objektumorientáltsággal
a
hookok
nem
adott
objektumokhoz kötődnek, hanem a Drupal oldalgenerálási folyamatának egy adott pontjához. Sőt mi több, a hookok működése állapotfüggő: különböző helyzetekben különböző feladatokat hajthatnak végre. A hookok lehetővé teszik, ahogy a modulok más modulokkal vagy akár magával a Drupal Core-ral is kommunikáljanak. Fontos, hogy a Hook API-nak [3] köszönhetően a fejlesztőknek lehetőségük van egy adott folyamatot vagy adatot több különböző ponton (több különböző modulban) is befolyásolni, ahelyett, hogy mindent egy adott helyen kellene megcsinálni. [1] Mivel a Hook API azt is lehetővé teszi a fejlesztőknek, hogy a létező hookokon felül saját hookokat is definiáljanak (amelyet aztán más modulokban is lehet használni), ezért az egész hook rendszer átláthatósága érdekében egységes elnevezési szabályzatot (naming standards) vezetek be. Két eset lehetséges: a fejlesztő egy meglévő hookot szeretne használni saját moduljában vagy egy saját hookot szeretne definiálni a saját moduljában, amelyet aztán bármely más modulban fel lehet majd használni.
19
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Létező hook megvalósítása saját modulban: Ehhez nem kell más tenni a fejlesztőnek, mint követni a Hook API erre vonatkozó elnevezési szabályait, ami szerint a saját függvény neve az alábbiak szerint épül fel: 1. A saját modul neve, amelyet egy aláhúzás jel követ. (Pl.: demo_) 2. A használni kívánt hook neve. Tehát, ha a hook_node_info() [7] hookot szeretnénk megvalósítani, akkor a modulunkban a demo_node_info() függvényt kell definiálnunk. Saját hook definiálása saját modulban: Ehhez a modulunkban a kívánt ponton meg kell hívnunk a module_invoke_all() [8] Drupal függvényt, amely meg fogja hívni az összes olyan modul összes olyan függvényét, amely az adott hookot implementálja. Konkrét példa: demo.module
fájl tartalma:
fájl tartalma, amely felhasználja a Demo modulban definiált introduce
hookot:
A Drupal hookokról és használatukról további hasznos információt a Drupal API ide kapcsolódó oldalán érhetünk el [3].
20
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
2.2.
A modulok felépítése
Ahogy már fentebb említve volt, a Drupal nagy előnye, hogy modulárisan épül fel, a rendszer bármilyen tetszőleges funkcionalitással bővíthető saját modulok segítségével, amelyeket utána bármely más Drupal oldalon is újra fel lehet használni. A modulok elkészítésére van pár egyszerű szabály, amely leírja azok szerkezeti felépítését és fájljainak tartalmát. A fájlokban található kódok szintaktikájára a Drupalnak saját coding standard-je van.
2.1. ábra – Demo modul felépítése
Íme a Demo modul, amely példaként szemlélteti, hogy egy modul fájljai milyen fájlstruktúrában helyezendőek el. [2.1. ábra] Természetesen a fentebbi struktúra még tovább bontható további almappákra is (pl.: a Views modulhoz tartozó saját kiegészítő osztályokat a demo/views mappába célszerű elhelyezni.). Ahhoz, hogy egy modul létezzen a Drupal rendszerben a fenti két fájl létezésére van szükség, azaz: a modulnév.info
és a modulnév.module . Pár szó ezekről a fájlokról:
a) demo.info Az info fájlok úgynevezett meta adat tároló fájlok Drupalban. Céljuk, hogy értesítsék a rendszert a modul (vagy smink) létezéséről, leírják annak függőségeit, adminisztrációs célokat lássanak el, stb. Íme a Demo modulunk info fájljának tartalma. Ahhoz, hogy a modul létezzen a rendszerben minimálisan elég lett volna a name és a core információt megadni az alábbi fájlban. Plusz információként hozzá lett adva még:
egy leírás (description), amely a modulok adminisztrációs oldalán jelenik majd meg,
egy függőség (dependencies), miszerint a modul bekapcsolásához a User modulnak léteznie kell és be is kell kapcsolva lennie.
valamint, hogy a modul a „Szakdolgozat” csomag (package) része. (Ez is csak a modul adminisztrációs oldalán jelenik meg.) 21
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
name = Demo description = Demo module. package = Szakdolgozat core = 7.x dependencies[] = user
b) demo.module Ahhoz, hogy a modul önmagában létezzen a Drupal rendszerben ez a fájl maradhatott volna akár üresen is. Azonban a szintaktika és a coding standard röpke ismertetése céljából írtam egy köszöntő funkciót, amely a felhasználó belépésekor a Drupal beépített drupal_set_message() [4] függvényével üdvözli a felhasználót. A példakódon megfigyelendő a fájl elején @file leírás, illetve, hogy minden függvény előtt célszerű megadni többsoros kommentben, hogy az adott függvény mire is való, illetve milyen hookot implementál. name), 'status'); }
A modulok ezeken kívül persze még további tetszőleges fájlokat is (pl.: inc, js, css, jpg, stb.) fájlokat tartalmazhatnak. Ezek használatáról és a modulok felépítésével kapcsolatos további információkról a drupal.org „Creating modules in Drupal 7” [13] oldalán lehet olvasni.
22
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
2.3.
A telepítési profilok
Valahányszor
frissen
telepítünk
Drupalt
a
webszerverünkre,
akkor
rögtön
találkozhatunk a Drupal két beépített telepítési profiljával a Minimallal és a Standarddal. A két profil fő különbsége a telepített modulok mennyiségében rejlik. A telepítő profilok fontossága akkor kerül előtérbe igazán, amikor úgynevezett „out of the box” terméket akarunk előállítani, amely a telepítés után már használható is az összes szolgáltatásával. A telepítési profilok felépítésükben hasonlóak a modulokhoz, ugyanúgy rendelkeznek egy profilnév.info és egy profilnév.profile fájllal, amely a modulnév.module .info
fájllal vonható párhuzamba. Amikor a telepítő fut a telepítési profil
fájljában található modulfüggőségek (depedencies) települni fognak, és a
telepítési profil .profile fájljában megfelelően definiált összes feladat (task) le fog futni. A telepítési profilok ezeken kívül még rendelkezhetnek egy (a modulok felépítésénél nem említett, de ugyanúgy ott is létező) .install fájllal is, amelyben különböző adatbázissal kapcsolatos műveleteket (táblák létrehozása, módosítása, stb) hajthatnak végre a hook_install() [5] függvény segítségével. Hogyan adhatunk hozzá saját feladatot a saját demo_profile telepítési profilunkhoz?
Ehhez
a hook_install_tasks() [6] függvényt kell megvalósítunk saját profilunk
demo_profile.profile
fájljában, amely az
elvégzendő feladatokat
tartalmazó
asszociatív tömbbel fog visszatérni. Íme egy rövid példa: /** * Implements hook_install_tasks(). */ function demo_profile_install_tasks(&$install_state) { $task['welcome'] = array( 'display_name' => st('Welcome'), 'display' => TRUE, 'type' => 'normal', 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED, 'function' => '_demo_profile_welcome', ); return $task; }
23
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben /** * Say "Welcome" to user. */ function _demo_profile_welcome() { drupal_set_message('Welcome', 'status'); }
A fentebbi példa létrehoz egy Welcome taskot, amely megjelenít egy üdvözlő üzenetet. [2.2. ábra]
2.2. ábra – Saját feladatunk a Drupal telepítőben
A feladatot tartalmazó tömb elemek rövid, nem teljes körű magyarázata:
display_name – a telepítés során megjelenítendő név.
display – a feladat megjelenjen-e a telepítő feladatainak listájában, vagy sem.
type – a feladat típusa, lehet: o normal – HTML-t ad vissza, amely oldalként lesz megjelenítve a telepítőben. Fontos hogy garantáljuk az innen való továbblépés lehetőségét (pl.: egy „Folytatás” gombbal). o batch – olyan tömböt ad vissza, amit a Batch API [11] kezel majd. A Batch API olyan feladatok elvégzésére való, amelyek hosszan futnának (pl.: importálás). A feladat akkor lesz késznek titulálva, amikor a Batch API végzett; ekkor a telepítő automatikusan továbblép a következő lépésre.
24
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
o form – egy Form API [20] tömböt ad vissza, amely űrlapként lesz megjelenítve az oldalán. Amikor az űrlap el lesz küldve a telepítő szintén automatikusan továbblép a következő lépésre.
run – a feladat futtatásának módja, ami lehet: o INSTALL_TASK_RUN_IF_REACHED – ez a feladat mindig fusson le, ha a telepítő az adott állapotot eléri (pl.: erőforrások importálása). o INSTALL_TASK_RUN_IF_NOT_COMPLETED – ez a feladat csak egyszer fusson telepítés során. o INSTALL_TASK_SKIP – ez a feladat ne fusson le a telepítés során.
function – a függvény neve, amelyet meg kell hívni az adott feladat során. Ha nincs megadva, akkor ez ugyanaz, mint a $task tömbben megadott index. (Esetünkben a „welcome”.)
További hasznos információkat a telepítési profilokról a drupal.org „Install Profile API” [21] oldalán vagy Isaac Sukin [36] blogjában olvashatunk.
25
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
3. AZ OPENSPACE FEJLESZTÉSE 3.1.
Így fejlesztünk mi!
A cégben, amely számára az Openspace készül a fejlesztők távmunkában dolgoznak. Ennek következményeképpen nincs egy központ géppark, amelyen a fejlesztés zajlana. Mindenki a saját számítógépén végzi a fejlesztést és az ehhez kapcsolódó mottónk körülbelül így foglalható össze: „Mindegy milyen eszközt használsz, ha azt tudod hatékonyan használni!”. Ez kiterjed operációs rendszerre, webszerverre, php fejlesztő szoftverre, bármilyen fejlesztéshez szükséges programra… egyetlen megkötést tudnék csak mondani: verziókezeléshez GIT-et használunk. Ez a megkötés azonban igen fontos, hiszen többen dolgozunk egyszerre az elkészülő Drupal oldalakon és ez verziókezelő szoftver nélkül lehetetlen lenne hatékonyan. Tehát a fejlesztést mindenki a saját számítógépén végzi (a saját kedvenc konfigurációimat már feljebb kifejtettem), a kódbázisok pedig egy központi szerveren tárolódnak. Minden oldalnak külön kódbázisa van külön git és ssh hozzáféréssel. Az adott oldalon dolgozó fejlesztőknek joguk van klónozni a git repót, commitolni és pusholni bele, illetve ssh-n keresztül belépve akár drush-t is használni a szerveren. Mivel többen dolgozunk egyszerre, ezért különböző funkciókat különböző branchekben fejlesztünk le, amelyeket aztán mergelünk a dev majd a prod branchbe. Minden oldalhoz tartozik az éles oldalon (prod branch) kívül egy fejlesztői oldal is, ahol a dev branchbe merge-ölt fejlesztéseket lehet tesztelni, review-zni és csak utána átvinni azokat az éles oldalra is. Ezzel is ügyelünk a kód minőségére és az ügyfél elégedettségére, mivel semmi sem mehet ki az éles oldalainkra anélkül, hogy az ne lett volna tesztelve az adott oldalhoz kapcsolódó dev oldalon (akár az ügyfél által is.) Ezt a fejlesztési módszert azonban bármely más webes projektnél is ugyanígy lehetne használni, de az igazi innovációt és használhatóságot a GIT és a Features [19] modul kombinációja adja a Drupal fejlesztéseink során. A Features modul lehetővé teszi, hogy a Drupal és a használt modulok beállításait egyszerűen kódba exportáljuk, majd azt egy másik Drupal oldalon ugyanilyen egyszerűen beimportáljuk. Az exportált beállítások modulok formájában generálódnak le, amelyeket nevezhetünk egyszerűen feature-nek is. (Egy feature például tartalmazhat 26
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
egy galéria funckcionalitást, amely áll egy tartalomtípusból, egy Views nézetből, képstílusokból, jogosultságokból, stb.) Miért hatalmas dolog ez? Azért mert a Featuresnek és a GIT-nek köszönhetően a lokális fejlesztő által végzett beállítások egyszerűen eljuttathatóak a fejlesztői és éles oldalra vagy a többi fejlesztő fejlesztői környezetére. Nincs szükség arra, hogy a beállításokat adatbázis dumpok formájában juttassuk el egymáshoz, vagy minden beállítást csak az éles oldalon végezzünk el, miközben mások is dolgoznak rajta. (Ez a workflow nem is támogatná hatékonyan, hogy többen fejlesszünk ugyanazon az oldalon, hiszen könnyen kitörölhetnénk, elronthatnánk más munkáját, illetve minden beállítás csak egy adatbázisban lenne meg. Hogyan lehetne akkor teljesen elszeparált dev és prod siteunk?) Így azonban az adott feature bekapcsolásával egy Drupal oldalon települ minden szükséges beállítás és a funkció már használható is. A Feature modul mellé még ajánlott használni a Strongarm [32] nevű modult, amellyel a Drupal és a modulok által használt rendszerváltozók értékét is feature-ökbe exportálhatjuk. Ezeknek a moduloknak a nagy előnye, hogy nem csak egy API-t definiálnak használatukhoz, de az exportálási műveletet egy egyszerű felhasználó interfész segítségével tudjuk megoldani. [3.1. ábra]
3.1. ábra – Features modul felhasználói interfésze Strongarm modullal bővítve
27
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
A Features-nek azonban vannak ismert hátrányai és hiányosságai, amelyekkel lehet együtt élni. Sok mindent lehet exportálni feature-be, sokféle modul sokféle beállításait, azonban egy dolgot nem: az oldalon létrehozott tartalmakat. Erre Drupal 7-ben vannak más modulok, amelyek szintén kompromisszumok árán, de használhatók erre (pl: UUID [34] + UUID Features [33], Node export [24], Default content [15]), azonban instabilitások miatt nem részei szervesen az általunk használt workflow-nak. Ez az egyetlen oka annak, amiért olykor adatbázis dumpokat kell megosztani a fejlesztőknek egymással vagy dev szerveren létrehozni minta tartalmakat, hogy meg tudják egymásnak mutatni a probléma forrását, vagy demózni tudjanak egy kész funkcionalitást. Ezt a fejlesztési módszert alkalmaztuk az Openspace létrehozása során is.
3.2.
Hozzáadott értékeim a rendszerhez
3.2.1. A telepítő profil további bővítése Az Openspace a kezdetektől fogva úgy készült, hogy később „out of the box”, azaz azonnal telepíthető termékként eladható legyen. Ennek egy fontos kelléke az, hogy a hozzá fejlesztett funkciókból a telepítés során tetszőlegesen lehessen bekapcsolni azokat, amelyekre szükségük lesz a felhasználónak. Az Openspace az elején egyszerű ügykezelőrendszernek lett csak tervezve, azonban az idő haladtával a cégnek igénye lett egy olyan eszközre is, amelyben az újonnan bevezetendő agilis fejlesztési módszerét tudja menedzselni. Mivel az ügykezelőrendszer fejlesztése még gyerekcipőben járt ekkor is, ezért célszerűnek tűnt ezt a funkcionalitást is az Openspace-ben megvalósítani. Erre a funkcióra azonban valószínűleg nem minden felhasználónak lesz szüksége, ezért született meg az Openspace saját telepítő profiljában a „Select extra features” telepítési feladat (task), amelyben az agilis fejlesztéshez használt Kanban funkcionalitást is lehet bekapcsolni a telepítés során.
28
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben /** * Implements hook_install_tasks(). * * @param $install_state * An array of information about the current installation state. * * @return array * An array of custom install tasks. */ function iq_profile_install_tasks($install_state) { return array( 'iq_profile_install_select_extra_features_form' => array( 'display_name' => st('Select extra features'), 'display' => TRUE, 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED, 'type' => 'form', ), 'iq_profile_install_extra_features' => array( 'display_name' => st('Install extra features'), 'display' => TRUE, 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED, 'type' => 'batch', ), ); }
Ez a telepítési feladat először csak a Kanban fejlesztési funkcióból állt, később belekerültek olyan extra funkcionalitások is, amelyek megkönnyítik a fejlesztői környezet telepítését: teszt felhasználók automatikus létrehozása, fejlesztői modulok (pl.: devel, views_ui, mailsystem, stb) bekapcsolásának lehetősége. [Mellékletek 1.1] [3.2. ábra]
3.2. ábra – A telepítőhöz hozzáadott „Select extra features” oldal
29
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
A telepítő csak kiválasztott plusz funkciókat telepíti. A kiválasztott funkciók Batch API műveletként hajtódnak végre, mivel több feladat is végrehajtódik a funkciók telepítése során, amely hosszúra nyúlhat. A választható funkciókról pár szót:
Kanban Development Tools – a Kanbanban történő agilis fejlesztéshez szükséges az iq_users_story() feature bekapcsolása, amely tartalmaz mindent, amely ehhez a funkcióhoz szükséges. A feature telepítése után szükséges a feature úgynevezett revertelése, amely gondoskodik arról, hogy a rendszer összes beállítása a feature kódjában tároltakkal egyezzen meg. Erről a $batch['operations'][]
=
array('features_revert',
array());
sor
gondoskodik.
Add test users – néhány teszt felhasználó létrehozása, amelyekkel telepítés után egyszerűbb tartalmakat létrehozni, funkcionalitásokat tesztelni. A felhasználók a telepítés során a nevükben található „en” és „hu” prefixek alapján különböző nyelvi beállításokkal települnek, így könnyen tesztelhetővé válik a felhasználói interfész többnyelvűsége is. Valamint azok a felhasználók, akiknek a neve „pmanager” szót tartalmazzák admin csoporttagságot kapnak, amelynek köszönhetően egyszerűbben tesztelhetőek a Drupal felhasználói csoportokra vonatkozó jogosultság beállítások is.
IssueQ debug – a már említett fejlesztéshez hasznos és szükséges modulok bekapcsolása, amelyekre csak a fejlesztőknek lesz szükségük. Továbbá az ezekhez kapcsolódó plusz beállítások bekapcsolása is, mint például a Devel modul [17] által szolgáltatott DevelMailLog mentse fájlokba az oldalról kimenő maileket, vagy a Switch user blokk legyen bekapcsolva az első oldalsávban. Ennek segítségével az előre telepített felhasználók közt egyszerűen lehet majd váltani, nem szükséges külön-külön ki- és belépni velük.
3.2.2. A jogosultságrendszer megvalósítása Az általunk elkészített ügykezelőrendszer legfontosabb alapköve a jogosultságrendszer, amely megszabja az oldalon regisztrált felhasználók jogosultságkörét. A rendszer komplexitását az adja, hogy a rendszerben nem csak a különböző felhasználói csoportokhoz tartoznak jogosultságok, de az egyes felhasználónak különböző felhasználó jogosultságai lehetnek különböző projekteken belül is. Ennek a 30
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
megvalósításához
már
a
kezdetekkor
tudtuk,
hogy
a
Drupal
beépített
jogosultágrendszere nem lesz elég, az Organic Groups (OG) modulhoz [26] kell fordulnunk. A megvalósításhoz használt vagy kipróbált modulok Abban elég biztosak voltunk, hogy az említett modul majd segít megvalósítani számunkra a szükséges funkcionalitást, azonban szolgáltatásainak és működésének kiismerése korántsem volt egyszerű feladat. A modul magyar neve „Öntevékeny csoportok”, amely valóban elég jól tükrözi, hogy mire is használható ez a modul. Az OG a Drupal jogosultságrendszere felett, azt kiegészítve működik a hook_node_access() –re [7] támaszkodva. Lehetővé teszi, hogy a felhasználók saját felhasználói csoportokat hozzanak létre az oldalon belül, amely tagjai saját csoporton belüli jogosultságokkal rendelkezhetnek, amelyek a csoportban létező szerepkörökhöz
kapcsolódnak.
A
csoportokon
belül
létezhetnek
különböző
tartalomtípusok, amelyekhez csoporton belüli szerepkörük szerint férhetnek hozzá a csoport tagjai. Az így létrehozott csoportok kétféleképpen működnek az oldalon belül: privát és publikus. A privát csoportok tartalmaihoz csak a csoport tagjai férhetnek hozzá. Átjárás az ilyen csoportok között csak a csoportban való tagsággal lehetséges, amely történhet automatikus jóváhagyással, csoportadminisztrátori jóváhagyással vagy csoportadminisztrátori regisztráció által. Ettől eltérően azonban lehet publikus csoportokat is létrehozni, amelyek tartalmához minden regisztrált felhasználó hozzáférhet. A csoportok a Drupal tartalomkezelő rendszerére épülnek, ahhoz hogy csoportokat létre lehessen hozni létre kell hozni egy olyan tartalomtípust (node type), amelyet kijelölünk og group node-nak. Ez a mi esetünkben a Project tartalomtípus lett. Az összes többi tartalomtípusnál pedig megadható egy og_group_ref mező, amelyben lehetővé tesszük, hogy az adott tartalomtípusként (pl.: News, Issue, Documentation, stb.) beküldött tartalmakat hozzá lehessen rendelni egy létező csoport tartalomtípushoz. Az így létrehozott, csoporthoz kapcsolódó tartalmakra vonatkozni fog majd az adott csoportban érvényes jogosultságrendszer.
31
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Azt, hogy a különböző csoportnak kijelölt tartalomtípusokban milyen szerepkörök és azokhoz kapcsolódóan milyen jogosultságszabályok legyenek alkalmazva, külön adminisztrációs felületen szabhatjuk meg, amelyet a modul szolgáltat. Az OG globális adminisztrációs felülete az admin/config/group aloldalon érhető el, ahol nem csak a szerepkörökre és jogosultságokra való beállításokat találhatjuk meg, hanem a néhány egyéb Organic Groups-szal kapcsolatos alapbeállítást is. (Ehhez az oldalhoz alapesetben csak a Drupal adminisztrátor felhasználói illetve azok Drupal felhasználó csoportok férhetnek hozzá, akik megkapták a „Administer Organic groups permissions” jogosultságot az admin/people/permissions oldalon.) Az „OG Rules Overview” oldalon van lehetőségünk megtekinteni a különböző csoporttartalmakban létrehozott különböző szerepköröket. A mi esetünkben a Projekt tartalomtípusban az alábbi öt szerepkör van definiálva, amelyből kettőt alapból létrehoz számunka az OG, hiszen meg kell szabni, hogy mihez férhetnek hozzá a csoport tagjai (members) és azok, akik nem tagjai a csoportnak (non-members). Ezen kívül nekünk szügségünk volt még egy csoport adminisztrátori szerepkörre (administrator member), illetve külön-külön szerepkörre a fejlesztők (developer) és a megrendelők (customer) számára, mert szeretnénk majd külön kezelni, hogy ez a két csoport milyen tartalmakhoz fér majd hozzá. [3.3 ábra]
3.2. ábra – A Projekt tartalomtípushoz (csoporthoz) kapcsolódó szerepkörök listája
Az adott szerepkörhöz kapcsolódó konkrét jogosultságok szerkesztésére itt is van lehetőségünk, illetve megtehetjük ezt az „OG permissions overview” oldalon is, ahol hasonlóan a „OG roles overview” oldalhoz, először a csoport tartalomtípust kell kiválasztunk. Ezt követően a [Mellékletek 1.2] pontban látható terjedelmes, elsőre talán bonyolultnak tűnő, adott csoporton belül létező jogosultságszabályokat tartalmazó táblázat fogad minket. A táblázat három fő részre bontható fel:
32
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1. Organic groups UI – itt a csoport működésével kapcsolatos jogosultságokat állíthatjuk be (csoporttagság, csoport adminisztráció) 2. Organic groups – ez a része a táblázatnak igen hasonló a Drupal beépített nodeokra vonatkozó táblázatára, amely az admin/people/permissions oldalon látható, ahol a különböző tartalomtípusokra vonatkozóan adhatjuk meg, hogy ki hozhatja létre, szerkesztheti, illetve törölheti azokat. Itt is ezt tehetjük meg, csak itt a csoporthoz kapcsolódó tartalomtípusokkal. 3. Organic groups field access – itt pedig lehetőségünk van megszabni, hogy a különböző csoporthoz tartozó tartalomtípusokban a különböző mezőkhöz kik férhetnek hozzá: kik láthatják az értékeiket és kik szerkeszthetik azokat. Ezek közül a 2. és a 3. pont az egyik leghasznosabb funkcionalitás az Organic Groups jogosultságrendszerében. Ezeknek köszönhetően tudjuk megtenni azt, hogy minél jobban elszeparáljuk jogosultságok szintjén egymástól a developer és a customer szerepkör
tagjait.
Például: Documentation típusú tartalmat nem hozhat létre customer felhasználó, azonban Issue tartalmat létrehozhat ő és a developer is. Itt a különbség a mezők hozzáférhetőségében gyökerezik, ugyanis a customer felhasználó nem állíthatja be az issue prioritását. Ő létrehozhatja azt, de ennek a beállításának jogát meghagyjuk a fejlesztőnek vagy a projektmenedzsernek, mivel a customer számára az összes ügy valószínűleg a legnagyobb prioritást élvezné és így átláthatatlan lenne, hogy melyik ügyek a valóban fontosak. A fentebb említett jogosultsággal kapcsolatos beállítások (csak) megtekinthető formában elérhetőek egyébként az adott csoport adminisztrációs felületén is, amely a node/[CSOPORT AZONOSÍTÓ]/group
oldalon elérhető, vagy a csoport oldalán a „Group”
fülre kattintva. Ehhez az oldalhoz csak a csoport adminisztrátora fér hozzá, ahol lehetősége van még a csoport felhasználóinak is a kezelésére is.
33
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
3.3. ábra – A „Demo Project” csoport adminisztrációs oldala
A felmerült problémák és a megoldásuk Ahogy említettem, abban biztosak voltunk, hogy az Organic Groups lesz a megoldás az Openspace-be tervezett jogosultságrendszer megvalósítására, azonban a modult nem ismertük töviről hegyire. Azt hiszem az is belátható, hogy a fentebb taglalt rengeteg jogosultságszabályozással kapcsolatos funkcionalitásnak köszönhetően ez nem is mondható egyszerűnek. Mivel először nem láttuk át ennyire, hogy hogyan is tudjuk megszabni
majd a csoportokra-, tartalomtípusokra-
és
szerepkörökre kívánt
jogosultságokat, ezért nekifogtunk megtervezni egy jogosultságtáblázatot. A tervezés során két táblázat született, a Permissions maze [Mellékletek 1.3] és a Permissions axes [Mellékletek 1.4]. Az első táblázat volt a legkorábbi, legrészletesebb és beláthatóan a legnehezebben lekódolható, majd utána letesztelhető. A megközelítés lényege az volt, hogy a szükséges szerepköröket figyelembe véve definiáljunk minden jogosultságot, ami csak szóba jöhet, felmerülhet a rendszer kapcsán. A jogosultságok halmaza azonban ezen megközelítés szerint mondatni „konvergált a végtelenbe”. Azon kívül, hogy a tartalomtípusokat a CRUD (Create/Read/Update/Delete) jogosultságok mentén felbontottuk, még hozzá kellett adnunk szinte minden esethez a „bármely [tartalomtípus]/saját [tartalomtípus]/projekt amelynek tagja, amely [tartalomtípus]…” eseteket is. Plusz, ha még ez nem lett volna elég problémás, akkor a tartalmakhoz tartozó különböző mezőkkel is el kellett ezt játszanunk, hiszen mezők szintjén is meg akartuk adni, hogy adott felhasználó hozzáférhet-e adott mezőnek a tartalmához, láthatja-e, módosíthatja-e azt. (Az Issue tartalomtípus mezőihez az Openspace-ben hozzá lehet férni a hozzászólások szintjén is, hozzászóláskor is lehet módosítani egy mező értékét, tehát ezzel is foglalkoznunk kellett.) Ezeken kívül pedig a kezdetek 34
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
kezdetén a szerepkörök esetében is a szerepkörök kombinációiról kellett beszélnünk, mert a Drupal beépített szerepkörei felett léteztek a saját OG szerepköreink is. Végül azonban ezeket is sikerült lecsupaszítanunk a táblázatban látható 2 Drupal szerepkörre és 4 OG szerepkörre. Azért tehettük ezt meg, mert a rendszerben alapfeltétel az, hogy a felhasználónak rendelkeznie kell felhasználói fiókkal az oldalon, ezért csak az „authenticated user” (belépett felhasználó) szerepkörrel kellett törődnünk. Ha egy felhasználó authenticated user, akkor már csak azt kell megvizsgálnunk, hogy az adott felhasználó tagja-e valamely csoportnak, és ha igen, akkor azon a csoporton belül milyen jogosultságokkal rendelkezik. (Ezért üres az authenticated user oszlop több helyen, hiszen evidens, hogy arról a jogosultságról akkor és csak akkor beszélhetünk csak, ha be van lépve a felhasználó az oldalra.) Ez a felismerés megkönnyítette az életünket, de még mindig ott volt a közel végtelen számú funkció, amelyhez meg kellett adnunk, hogy ezek a jogosultságok miként férnek hozzá. A táblázat kitöltésre került legjobb tudásom szerint, azonban a tartalmak típusokhoz kapcsoló jogosultságoknak itt-ott már-már értelmezhetetlen megnevezésük lett. Erre akkor döbbentünk rá leginkább, amikor pár hét után elővettük a táblázatot, hogy átbeszéljük mindenhol helyesen lett-e kitöltve, azonban sok helyen már a funkció nevét se tudtuk értelmezni. Ha pedig mi nem tudjuk értelmezni, akkor hogyan kódoljuk le azt, hogyan írjunk rá később teszteket? Arról nem is beszélve, hogy a táblázat ekkor 218 funkcióból állt, és ahogy elkezdtük értelmezni a táblázatot, abban sem voltunk biztosak, hogy ez mindent tartalmaz. Új megközelítésre volt tehát szükség, amely visszamegy a kaptafához és nulláról újragondolja az egész jogosultságrendszer alapjait. Ezt a Permission axes [Mellékletek 1.4] táblázataiban láthatjuk. Megpróbáltuk a Permission maze táblázatban látható tartalomtípusokhoz kapcsolódó funkciókat különböző logikai szempontok szerint csoportosítani. A cél az volt, hogy ha meglesznek ezek a csoportosításaink, és a tengelyeken előforduló értékek száma, akkor ezek kombinációból már pontosabban meg tudjuk mondani, hogy valóban hány jogosultságra is lesz szükségünk tartalomtípusonként. Ezen kívül még pontosabb neveket tudunk majd adni a jogosultságnak, amivel pedig átláthatóbb lesz a jogosultságtáblázatunk. 35
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
A táblázat első verziója, amely a Kiinduló nevet viseli a mellékletben rémisztő eredményt hozott. Az implementálandó jogosultságok száma: 4 x 2 x 5 x 5 x 4 x 5 x 1 = 400 db / tartalomtípus,
amely szám szerint a lehetetlennel próbálkozunk. Ezért
elkezdtük felülvizsgálni ezt a táblázatot is és kivenni belőle azokat a csoportokat és tengelyeket, amelyekre indokoltan nincs szükségünk:
CRUD szerint – Ezt a csoportot nem vehetjük ki, ez minden rendszer alapját képezi.
Beküldő szerint – Ezzel egyszerűen úgy döntöttünk, hogy egyelőre ne foglalkozzunk még. A rendszer enélkül is működő képes lesz.
Felelős szerint – Ez a csoport feleslegesnek bizonyult, ugyanis miután átgondoltuk nem találtunk olyan jogosultságot, amely attól függne, hogy egy adott felhasználó felelőse-e az adott tartalomnak vagy sem. Egyébként is, ez a csoportosítás csak két tartalomtípusra húzható rá az ötből és felelősről csak akkor beszélhetünk, ha az adott felhasználó egy projekt tagja, ha pedig egy projekt tagja, akkor majd használjuk a projektben létező jogosultságát, ha szükséges.
OG szerepkör – Szintén megmaradt. Többször leszögeztük, hogy ezekre a szerepkörökre az OG csoportokban (projekteken belül) szükségünk lesz.
Site szerepkör szerint – Itt a Drupal szerepkörei gondoltunk és rájöttünk, hogy nekünk nincs szükségünk Drupal szerepkörök szintjén Developer, Customer szerepkörre, hiszen ők rendszer szinten semmivel nem fognak többhöz hozzáférni, mint a beépített Authenticated user szerepkör. Ugyancsak nem kell számolnunk az Anonymus (be nem lépett felhasználók) szerepkörrel, mert ők semmihez sem férhetnek hozzá. Ezekkel a baltavágásokkal azt kaptuk, hogy összesen már csak 4 x 5 x 1 = 20
db / tartalomtípus
jogosultság definiálására lesz szükségünk, amelyek nevét jóval
egyértelműen meg tudjuk adni: „Belépett felhasználó, aki Developerként tagja a Projektnek szerkesztheti-e projekthez beküldött Dokumentáció tartalmat.” Ha még ezenkívül a tartalomtípusok egyes mezőinek hozzáférését is definiálni kell ezen táblázat szerint, akkor is belátható időn- és jogosultságszámon belül készen tudunk vele lenni.
36
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Összefoglalás Miután elkészültünk az Permission axes táblázattal és jobban tisztában voltunk azzal, hogy hogyan is fog felépülni a jogosultságrendszer, akkor villámként csapott belénk a gondolat: ehhez hasonló csoportosítást már láttunk valahol. A hely pedig a [Mellékletek 1.2] – n látható OG csoporton belüli jogosultságtáblázata, amely már kezdetektől létezett, hiszen a Projekt tartalmunk og groups node-ként volt definiálva. Sőt mi több ez a táblázat nem csak tartalomtípusként tartalmazta az általunk megtervezett jogosultságokat is, de a tartalomtípusok mezői szintjén is, tehát ezzel sem kellett foglalkoznunk. Végül tehát mondhatni kódírás nélkül, csak alapos és mindent átható tervezéssel meg tudtuk valósítani azt, amit az Organic Groups modul alapból szállított számunkra. Azonban ezek után se éreztük időpocsékolásnak a jogosultságrendszerrel töltött időt, ugyanis ennek segítségével magának az OG modul jogosultságrendszerének a magját is sikerült megértenünk. Ezen kívül pedig jó pár hibát is sikerült kiküszöbölnünk a többszöri újratervezés során. (Pl.: a felesleges Drupal szintű Developer és Customer szerepkört.)
3.2.3. Az értesítési rendszer Az Openspace másik legfontosabb és legszükségesebb funkciója az értesítési rendszer volt. Ennek a funkciónak az implementálásával kezdtem el leghamarabb foglalkozni, még 2013 tavaszán, ami azért fontos dátum, mert ekkoriban még valóban nem volt olyan modul, amely jobb választásnak tűnt volna, mint a Notifications. Az ezzel a modullal járó problémákra már utaltam a felelős szerkesztőről szóró részben, most részleteiben is ki fogom fejteni őket. Az értesítési rendszer kapcsán egyik fontos tervünk volt, hogy olyat hozzunk létre, amely nem csak emailen keresztül, de mondhatni bármely csatornára képes lesz üzenetet továbbítani (pl. IRC, SMS, Push notifications, stb). Egyetlen modult ismertünk, amely képes erre: a Messaging [23], amely minő meglepetés, a Notifications modul telepítési feltétele. Tehát minden kézenfekvőnek tűnt, hogy ezt a modult használjuk, lássuk azonban a rideg valóságot. 37
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
A megvalósításhoz használt vagy kipróbált modulok A Notifications [25] modult nevezhetnénk a Drupal 7-es világ fekete bárányának is. Funkcióját beteljesíti, valamilyen szinten jól is használható, azonban ismertek a hátrányai, hiányosságai is. Drupal 6 világában nem létezett nála jobb megoldás ilyen értesíti rendszer készítésére, és amikor elkezdtem a funkció elkészítését, akkor úgy tűnt a 7-esben sincs jobb. Egyetlen ígéretesnek tűnő projekt az akkor még frissnek mondható Message Notify [22] modul volt, de ennek idején még nem volt akkora felhasználói köre. Ez a modul azonban mostanra kinőtte magát (az Open Atrium 2-nek [40] is része lett) és valószínűleg mostani fejemmel emellett a modul mellett döntenék a Notificaitons-el szemben. Azonban volt pár nyomós érv anno a Notifications mellett, mint például az Organic Group Notifications modul létezése. Ahogy a leírásában is hirdeti a modul lehetővé teszi az OG csoportokon belüli feliratkozásokat, értesítéseket, amire nekünk pont szükségünk volt az új rendszerben. Sajnos, mint kiderült ennek a modulnak még rosszabb a támogatása, mint a Notifications-nek. Ezek után lássuk mikre volt jól használható a Notifications és az OG Notifications modulok kombinációja és milyen problémákba ütköztem a használatuk során. A felmerült problémák és a megoldásuk Az aranyszabályt előre megfogalmaznám a Notifications-szel kapcsolatban: addig, amíg csak arra van szükséged, amit a modul alapból szolgáltat számodra és felhasználói interfészen beállítható, addig a modul tökéletesen használható. Amint olyan funkciót szeretnél megvalósítani, ami eddig nem volt a modul része meg fogsz rekedni, mert nem kapsz a modulhoz támogatást. Mindent a „Csináld magad!” módszerrel kell megoldanod egy olyan kódbázison, amely rosszul dokumentált. Maga a Notifications modul csak a keretrendszert adja az értesítési rendszer mögé. Nekünk kell kiválasztani a modullal érkező almodulok közül, hogy milyen feliratkozásokat szeretnénk lehetővé tenni az oldalon. Mivel szükségünk volt a tartalmakra- és az új címkéző rendszer által szolgáltatott tagekre való feliratkozásra is, ezért bekapcsolásra került közülük a Content Notifications és a Taxonomy Notifications modul is, valamit a Notifications UI, amely lehetővé teszi a modul felhasználói
38
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
felületről történő konfigurálását az admin/config/messaging oldalon. [3.5. ábra] Első lépésként nem voltunk nagyravágyóak, ezért csak az egyszerű szöveges email értesítést volt feladatom megoldani, amelyhez pedig a Notifcations függőségeként telepített Messaging modulból be kellett kapcsolnom a Simple Mail almodult.
3.5. ábra – A Notifications modul adminisztrációs felületének feliratkozásokra vonatkozó része
Illetve, hogy lehetővé tegyük a csoportokra (projektek) történi feliratkozást, így bekapcsolásra került még az Organic Groups Notifications modul is.
1) Probléma Az Organic Groups Notifications modul már nem volt működőképes ekkoriban, mivel nem követte az Organic Groups modul változásait. Erre a hibára létezett több ügy is drupal.org-on a projekt issue queue-i között, azonban működő megoldással egyik sem szolgált. Első feladatom ennek a hibának az elhárítása volt. Miután tüzetesebben átvizsgáltam a modult sikerült megtalálnom a hiba forrását, amely abban rejlett, hogy a modul Og_Notifications_Subscription osztályában (amely az értesítési rendszer Notifications_Content_Subscription
osztályát hivatott kibővíteni és ezzel lehetővé
téve a csoportokra való feliratkozást is) a set_group($node) függvényében rosszul határozta meg a csoport id-jaként szolgáló gid változó értékét. A változó értékét kétféle módon lehet meghatározni, attól függően, hogy olyan node-ról beszélünk, amely 39
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
csoportként van definiálva (og group node), vagy olyan node-ról van szó, amely csoporthoz tartozó tartalomtípus (og group content node). A másik hiba az Og_Notifications_Field
osztályban volt az object_value($object) függvényben,
amely a tartalom id-jaként szolgáló nid-et volt hivatott visszaadni, azonban ezt szintén az előzőhöz hasonlóan rosszul határozta meg. Először csak ezekkel a hibákkal kellett szembesülnöm, amelyek javítása után a modul használhatóvá is vált. A modulhoz készítettem egy patchet, amelyet az egyik erről a problémáról szóló issue-nál [45] közzé is tettem a közösség számára tesztelésre. (A Drupal közösségben az mxr576 nick nevet használom.) A következő hiba már az Openspace tesztelésre és használatba vétele közben bukott
ki,
amelynek
object_value($object)
a
forrása
szintén
a
Og_Notifications_Field
osztály
függvényében volt. Az adott függvény ugyanis nem csak node
objektumokból próbálta meghatározni annak nid-jét, hanem hozzászólásokból is. Ezt egy egyszerű új feltétel hozzáadásával meg lehet akadályozni, amely megvizsgálta, hogy a függvény lefutásakor tartalomtípussal van-e dolga, vagy hozzászólással. Az előző ügyhöz beküldött patchet frissítettem [45] és a közösségből vissza is jeleztek, hogy megoldja a modullal kapcsolatos problémákat. Az elkészített teljes javító patch megtekinthető a [Mellékletek 1.5] pontjában. 2) Probléma Ez a probléma inkább mondható inkább elvárt funkcionalitásnak, mint problémának. Kétféle automatikus feliratkoztatási módra volt szükségünk az Openspace-be. Az egyik, hogy az újonnan regisztrált felhasználó automatikusan legyen feliratkoztatva a News tartalomtípus tartalmaira. Ez azért szükséges, mert a projekteken belül a News típusú tartalmak fontos információkat hordoznak az adott projekttel kapcsolatban (pl.: elérések, stb.), amelyekről a projekt összes tagjának célszerű értesítést kapnia. A kettő közül ez a feladat volt az egyszerűbb, ugyanis csak egy notifications_subscription() objektumot
kellett
létrehoznunk,
notifications_save_subscriptions()
majd
függvénnyel
elmentenünk a
a
hook_user_insert()
függvényben, amely akkor fut le, amikor új felhasználó készül a rendszerben. Íme, a teljes kód: 40
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
/** * Implements hook_user_insert(). */ function iq_notifications_user_insert(&$edit, $account, $category) { // Auto subscribe user to News CT's notifications. $subscription = notifications_subscription('content_type') ->instance() ->add_field('node:type', 'news') ->set_user($account) ->set_method('mail'); // Optional. notifications_save_subscription($subscription); }
A másik feliratkoztatási módot picit bonyolultabb volt megoldani. A feladat az volt, hogy az Issue tartalomhoz hozzárendelt felhasználó is legyen feliratkoztatva az adott Issue tartalomra. Miért hasznos ez? Ugyanis így a hozzárendelt felhasználó rögtön értesül arról, ha valaki egy ügyet rendelt hozzá, amely státuszától függően akár nagyon fontos feladatot is tartalmazhat, amelynek elvégzése nem várathat magára. Ennél a feliratkoztatásnál két dolgot kellett figyelembe venni. Az egyik az, hogy a hozzárendelés történhet már akkor, amikor valaki létrehozza az Issue tartalmat; a másik mód az, amikor módosítja valaki a már beküldött Issue tartalmat és akkor rendeli hozzá a felhasználót. Ez az eset Openspaceben különösen érdekes, ugyanis mint már említettem egy Issue tartalom nem csak a tartalom szerkesztése oldalon módosítható, hanem hozzászólás beküldésével is. Ennek a megvalósításához először létre kellett hozni egy saját actiont. Drupalban az actionök bizonyos események hatására futnak le, amelyekből van jó néhány előre definiált a rendszerben (pl.: felhasználó bejelentkezik, tartalom létrejön, stb), de akár definiálhatunk sajátokat is. Szerencsére a mi esetünk erre nem volt szükség. Elég volt csak egy saját actiont létrehozni, amely a „tartalom frissítésre kerül” (node_update) vagy a „tartalom közzétételre kerül” (node_publish) eseményekre fut re. Actiont
a
hook_action_info()
függvény
segítségével
definiálhatunk
saját
modulunkban.
41
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben /** * Implements hook_action_info(). */ function iq_notifications_action_info() { return array( 'iq_notifications_node_assignment_changed_action' => array( 'type' => 'iq_notifications', 'label' => t('Subscribe user to content thread if it\'s been assigned to him/her'), 'configurable' => FALSE, 'behavior' => array('sends_notification'), 'triggers' => array('node_update', 'node_publish'), ), ); }
Ezek
után
a
hook_action_info()
iq_notifications_node_assignment_changed_action()
implementációjában függvényben
látható kellett
definiálnunk, hogy mit is csináljon az action, ha ezek az események bekövetkeznek. /** * Auto subscribe user to current issue node if not subscribed yet. */ function iq_notifications_node_assignment_changed_action($node, $context = array()) { /** * Only on Issue nodes: on node_update event we need to check whether the * assignment of the issue has changed or not, and on node_publish event the * assigned user must be subscribed to the new content. */ if (isset($context['node']) && $context['node']->type == 'issue') { $prev_assigned_uid = isset($context['node']->original>field_issue_assigned_to[LANGUAGE_NONE][0]['target_id']) ? $context['node']->original>field_issue_assigned_to[LANGUAGE_NONE][0]['target_id'] : NULL; if ($context['node']->status && isset($context['node']>field_issue_assigned_to[LANGUAGE_NONE][0]['target_id']) && $context['node']>field_issue_assigned_to[LANGUAGE_NONE][0]['target_id'] != $prev_assigned_uid) { $account = user_load($context['node']>field_issue_assigned_to[LANGUAGE_NONE][0]['target_id']); $subscription = notifications_subscription('content_thread') ->instance() ->add_field('node:nid', $context['node']->nid) ->set_user($account) ->set_method('mail'); // Optional. notifications_save_subscription($subscription); } } }
42
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Először is meg kell adni, hogy ez az action csak akkor csináljon valamit, ha Issue tartalommal történik valami, mivel csak annál van lehetőség hozzárendelni azt egy felhasználóhoz. Ha ezt megvizsgáltuk, akkor nézzük meg, hogy változott-e a $context['node']->field_issue_assigned_to[LANGUAGE_NONE][0]['target_id']
változó értéke, amely a hozzárendelt felhasználó user id-ja (uid) tárolja. Ha változott az érték, akkor hozzunk létre egy feliratkozást az adott tartalomra, amely hasonlít a regisztrációkor létrehozott feliratkozásra, azonban itt csak erre az egyetlen tartalomra iratkoztatjuk fel az adott felhasználót, ezért kell az add_field('node:nid', $context['node']->nid)
függvényt
így
paraméterezni.
Majd
elmentjük
a
feliratkoztatást. Ezzel készen is vagyunk, már csak egy dolgot kell megtennünk. Megoldani, hogy
a
saját
iq_notifications_node_assignment_changed_action()
actionünk
automatikusan hozzá legyen rendelve a node_update és a node_publish eseményhez. Ezt az iq_notifications.install fájlban egyszerűen megtehetjük a hook_install() függvény implementálásával. Ez a függvény mindig lefut, amikor a modul bekapcsolásra kerül. /** * Implementation of hook_install(). */ function iq_notifications_install() { // Assign our action to triggers. $triggers = array('node_update', 'node_publish'); foreach ($triggers as $trigger) { notifications_content_install_trigger_action($trigger, 'iq_notifications_node_assignment_changed_action'); } // Decrease our action's weight to run before // 'notifications_content_node_post_action' action. db_update('trigger_assignments') ->condition('aid', 'iq_notifications_node_assignment_changed_action') ->fields(array('weight' => -10)) ->execute(); }
Ebben a lépésben még egy problémát kell orvosolnunk. A saját actionünknek előbb kell lefutnia, mint a Notifications által létrehozott és az értesítések kiküldéséért felelős
notifications_content_node_post_action()
notifications_content_node_update_action()
és
actionnek. Miért? Ha ugyanis a mi
függvényünk csak ezek után fut le, akkor a hozzárendelt felhasználó csak az adott Issue 43
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
következő eseményeiről (pl.: újabb módosítás, új hozzászólás, stb) fog értesítést kapni, magáról a hozzárendelést kiváltó eseményről nem. Majd pedig a modul kikapcsolásakor takarítsuk el magunk után a szemetünket, avagy töröljük a saját actionünk előző hozzárendelését a rendszerből, hiszen ha a modul nincs bekapcsolva, akkor az actionünk sem tud lefutni. /** * Implementation of hook_uninstall(). */ function iq_notifications_uninstall() { // Cleaning up our action's mess. db_delete('trigger_assignments') ->condition('aid', 'iq_notifications_node_assignment_changed_action') ->execute(); }
3) Probléma A Notifications által kiküldött email üzenetek kinézetének személyre szabása. A régi Drupal 6-os ügykezelőben volt egy jól megszokott kinézete a rendszer által kiküldött értesítési üzeneteknek, amely pont elég részletes volt ahhoz, hogy a felhasználó megfelelően tájékoztatva legyen arról, hogy miért is kapta az adott értesítést. Valami hasonló eredményt elérése volt a cél az Openspace-ben is, ahol jóval több
hasznos
információval
rendelkező
mezővel
rendelkeznek
a
különböző
tartalomtípusok, amelyek közül a legfontosabbnak szintén meg kellene jelennie az értesítések szövegében. A probléma forrása abban rejlett, hogy a Notifications az értesítések szövegének felhasználói felületen keresztül történő módosítására sem ad lehetőséget, tehát meg kellett találni a módot, hogy saját notifications template-eket hozzunk létre. A
legegyszerűbb
Notifications_Message_Template
módnak
erre
a
Notifications
saját
osztályának bővítése tűnt, ugyanis ebből származnak
a modul által kiküldött üzenetek template-jei. Ez az osztály egyébként a Messaging modul Messaging_Message_Template osztályát bővíti ki. (Megvan a kapcsolat forrása a Notifications és a Messaging modul között.) Így ha saját modulunkban kibővítjük ezt 44
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
az osztályt, akkor a későbbiekben lehetőségünk lesz használni a saját template-ünket akkor is, ha nem emailben, hanem más csatornán küldjük ki az értesítést. A
saját
osztályom
neve
Iq_Notifications_Node_Event_Template
lett.
[Mellékletek 1.6] Ez az osztály szolgál arra, hogy itt definiáljam az összes eseményre (pl.: tartalom létrehozása, tartalom frissítése, új hozzászólás, stb) jellemző notifications template alapot, amelyet majd ezek után lehet tovább specializálni az eseményeknek megfelelően. (Tehát ősosztályként szolgál majd a többi notifications template-jeim számára.) A feladat első lépéseként meg kellett határoznunk, hogy adott tartalomtípusokon belül melyik mezőket tartjuk elég fontosnak ahhoz, hogy részei legyenek a rendszer által kiküldött üzeneteknek. Mivel többféle tartalomtípusról beszélünk, amelyek különböző mezőkkel rendelkeznek, ezért szükségünk lesz a saját template fájlunkon belül valamire, amely megmondja, hogy milyen tartalomtípussal is van dolgunk éppen. Erre a Notifications_Message_Template ősosztály get_objects() függvénye lett a megoldás, amely visszaadja nekünk a template-ben elérhető objektumokat, amelyek közt megtalálható a node objektum is. Ezek után már csak a saját template-ünkben kellett felüldefiniálni a default_text() függvényt, amely egy helyen definiálja az emailek tárgyát és tartalmi részét. Ebben a függvényben csak egyszerűen meg kellett vizsgálnom, hogy elérhető-e a node objektum, és ha igen akkor a $node->type értékének megfelelően egy switch feltételes vezérlési szerkezeten belül kell megadni, hogy adott tartalomtípus esetében milyen mezők kerüljenek a bele template-be. Fontos volt még arra is ügyelni, hogy nem minden mező kitöltése kötelező a tartalomtípusokban, tehát csak azokat kell megjeleníteni az emailekben is, amelyek értéke meg lett adva. A tartalomtípusok body mezőjének rövid bevezetőjét is saját kezűleg kell legenerálni, amelyet az _iq_notifications_generate_teaser()
névre hallgató függvényemben oldottam meg.
[Mellékletek 1.7] (Erre azért volt szükség, mert a teljes body mező szövegét nem lenne szerencsés megjeleníteni a kimenő mailekben, mivel igen hosszúra is nyúlhat.)
45
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Miután elkészült a saját template ősosztályom, elkészítettem a három specializált változatát is, amelyek a tartalom létrehozásakor, a tartalom módosításakor és a tartalomhoz beérkezett új komment esetében módosítják a template ősosztály szövegét. Az Iq_Notifications_Node_Post_Template osztály fogja tartalmazni a tartalom létrehozásakor kimenő email szövegét. Ez esetben az ősosztálytól csak az email tárgyában kell eltérni, amelyet a default_text() függvényben írtam felül, de ezt megtehettem volna text_subject() függvény segítségével is. A tárgy szövegén belül is van még egy kisebb specializálás, ugyanis a projekt tartalomtípus esetében is eltérő tárgyat fog tartalmazni a kimenő email. Az Iq_Notifications_Node_Update_Template osztály tartalmazza a tartalom módosításakor kimenő email szövegét. Mivel a saját ősosztályomban az email tárgya pont az update esetet tartalmazza, ezért ezt az osztály lényegében nem különbözik az ősosztálytól, kivéve a digest_line($field, $options = array()) függvényben, amely más szöveget fog vissza adni a node:nid és author:uid mintára.
Az
Iq_Notifications_Node_Comment_Template
pedig
a
tartalmakhoz
beküldött
hozzászólások template-jét tartalmazza. Mivel Openspace-ben csak az Issue tartalmakhoz lehet hozzászólásokat beküldeni, ezért csak ezt az esetet kellett figyelembe venni, mégpedig ugyanúgy, mint a tartalmak beküldésénél is. Az ott hozzáadott extra Issue tartalomtípushoz kapcsolódó mezőket itt is meg kellett jeleníteni hasonló módon, illetve az email tárgyát és bevezető szövegét is módosítani kellett, ez utóbbit a text_header()
függvényben.
Attól azonban, hogy létrehoztuk ezeket a saját template osztályokat a Notifications modul még nem lesz képes rögtön használni őket. Ehhez előbb implementálnunk kell a hook_notifications() függvényt, ahol a message templateshez hozzá kell adnunk a saját template osztályinkat.
46
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben /** * Implements hook_notifications(). */ function iq_notifications_notifications($op) { switch ($op) { case 'message templates': // Register our custom templates. $types['iq_notifications-node-update'] = array( 'object' => 'node', 'title' => t('IssueQ Node Update'), 'class' => 'Iq_Notifications_Node_Update_Template', ); $types['iq_notifications-node-insert'] = array( 'object' => 'node', 'title' => t('IssueQ Node Post'), 'class' => 'Iq_Notifications_Node_Post_Template', ); $types['iq_notifications-node-comment'] = array( 'object' => 'node', 'title' => t('IssueQ Node Comment'), 'class' => 'Iq_Notifications_Node_Comment_Template', ); return $types; } }
Ha ezt megtettük, akkor a Notifications adminisztrációs felületén az admin/config/messaging/notifications/events
oldalon
lesz
lehetőségünk
hozzárendelni a saját template-tünket a különböző eseményekhez. [3.6. ábra]
3.6. ábra – A Notifications modul adminisztrációs felülete, ahol be lehet állítani az eseményekhez kapcsolódó templateket
Összefoglalás Miután a modul(ok) megkapták a szükséges javításokat és bővítéseket az általunk kívánt összes funkcionalitást sikerült megvalósítanom velük. Az értesítési rendszer azóta már használatban van és jól működik. Az azonban elmondható, hogy ennek a feladatnak a megoldása is rengeteg tanulságot hordozott magában. 47
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Ennek köszönhetően ástam bele legalaposabban magam egy nagyon összetett modul legmélyebb bugyraiba és tanultam meg milyen fontos, hogy jól válasszuk modult. A legfőbb szempontom innentől, hogy csak folyamatosan karbantartott és a lehető legjobb dokumentációval és támogatással rendelkező modult használjak fel. Legközelebbi projektem alkalmával (vagy az Openspace 2.0-ban) hasonló célokra a Message Notify modul használatával fogok megpróbálkozni. Az itt megtanultaknak nagy hasznát vettem a kereső funkció kifejlesztése során is.
48
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
3.2.4. A kereső funkció Ennél a funkciónál is jellemző volt, hogy tudtuk, hogy mit akarunk csinálni, mik a minimális elvárásaink a funkcióval kapcsolatban és volt sejtésünk már, hogy mivel tudnánk ezt megvalósítani. A sejtés a Search API [27] modul volt, mivel abban biztosak voltunk, hogy a Drupal core-al érkező beépített kereső funkció itt is kevésnek fog bizonyulni, csakúgy, mint a Drupal 6-os rendszerben is tette. Ez a modul az, amelyről sokan azt mondják, hogy végtelenül flexibilis és jól felhasználható bármilyen keresés megvalósítására. Összességében ezzel a funkció megvalósítása után sem tudnék vitatkozni, lehet az általunk elvártak voltak túl komplexek, amelyekre valóban csak különböző „trükkök” és saját megoldások segítségével lehetett rávenni a rendszert. A minimális elvárásaink közt olyanok szerepeltek, hogy szerettünk volna ha egyszerre lehet keresni a beküldött tartalmakban (node-ok) és a tartalmakhoz kapcsolódó hozzászólásokban is. Nem szerettük volna, ha az Openspace keresőjének szüksége lenne Apache Solr szerver [2] telepítésére, amely telepítése bonyolult és a legtöbb hoszting szolgáltató nem nyújt hozzá támogatást. Továbbá mivel a tartalomtípusok sok olyan mezővel rendelkeznek, amelyek hasznos és fontos információt hordoznak (pl.: az issue-nál a státusz mező értéke, a tartalmaknál a projekt mező értéke, stb), ezért hasznos lenne egy úgynevezett „faceted search” megvalósítása is a keresőben, amely lehetővé teszi a megjelenített keresési eredmények további szűrését a kiválasztott mezők értékeit csoportosítva. [3.7. ábra]
49
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
3.7 ábra – Faceted Search blokk, amely az adott keresési eredményeknél éppen elérhető plusz szűrési feltételeket tartalmazza
Ezek közül az alapjában véve egyszerűnek mondható elvárások közül a tartalmakban és kommentekben egyszerre történő keresés okozta a legtöbb problémát, mivel ezt a Search API nem támogatta natívan így a többi hozzá kapcsoló modulok is támogatási problémával küszködtek. A funkciót mindezek ellenére nem kevés idő feláldozásával, de sikerült megvalósítani minden elvárásunknak megfelelően, amely idő közben valóban volt jópár olyan próbálkozás, amely zsákutcának bizonyult. A megvalósításhoz használt vagy kipróbált modulok Tehát megvolt az elképzelésünk, hogy mit akarunk, és mivel akarjuk megcsinálni. A Search API modul mellé a Facet API modul [18] volt az, amely szolgáltatta számunkra a „faceted search” blokkot. Kellett még nekünk egy adatbázis kapcsolatot elősegítő Search API Database modul [28] is, amely a MySQL adatbázisban való keresést és indexelést fogja lehetővé tenni a Search API modul számára. (Mivel a Search API modul valóban igen flexibilis, ezért többféle database providerrel is együtt tud működni, ilyen például a Search API Solr search [31] is, amely az Apache Solr támogatást valósítja meg.)
50
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Első lépésben tehát bekapcsolásra kerültek a Search API, Database Search, Facet API, Search facets és Search Views modulok, amelyekből a még nem említettek a Search API modul részei. Ezek közül a Search Views a Views [35] modulhoz való támogatást valósítja meg, amelynek köszönhetően létre tudunk olyan nézetoldalakat hozni, amelyek a Search API által készített indexekben keresnek. Ezek telepítése után az admin/config/search/search_api oldalon [3.8. ábra] volt lehetőségem a Search API által szolgáltatott keresések megvalósítása.
3.8. ábra – Search API modul adminisztrációs oldala
A fentebbi adminisztrációs felületen először létre kellett hoznom az „Add server” menüpont segítségével egy szerver konfigurációt, amely megmondja a Search API-nak, hogy milyen adatbázisban milyen beállításokkal keressen és indexeljen majd. Ez a mi esetünkben a MySQL szerver volt, amelyhez ki kellett választanom, hogy a Database service-t szeretném Service class-ként használni (amelyet a Search API Database modul szolgáltat). [3.9. ábra]
51
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
3.9. ábra – A MySQL kereső szerver beállítása Search API modulban
Ezek után már csak a használni kívánt kereső indexeket kellett létrehoznom, amelyeknél első lépésként definiálni lehetett, hogy milyen entitásban (pl.: comment, node, user, stb.) keressen és indexeljen és ehhez melyik keresőszervert használja. Fontos beállítás még itt, hogy az újonnan létrehozott entitások automatikusan legyen indexelve, hiszen az újonnan létrehozott tartalmakra is szükség lehet a keresés során. [3.10. ábra]
3.10. ábra – A CommentIndex keresési index létrehozásának első lépése
52
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Két keresési indexet hoztam így létre, az egyik, amely a tartalomtípusokat indexeli (ContentIndex), a másik pedig a hozzászólásokat (CommentIndex). Minden egyes index létrehozása után lehetőségem volt az index beállítási oldalán megadni, hogy az adott indexbe az adott entitás mely mezői kerüljenek bele (Fields fül), az indexelés során milyen egyéb módosításokat, feladatokat hajtson végre a Search API a tartalmakon (pl.: jogosultság ellenőrzés) (Workflow fül) és az indexeléshez hozzáadott mezőkből melyikeket szeretném, ha elérhetőek lennének faceted search blokként (Facets fül). [3.11. ábra]
3.11. ábra – A ContentIndex keresési index beállításai (Facets fül)
Ezek után akadtam rá az első problémára, amikor létre szerettem volna hozni egy olyan kereső oldalt a Views segítsévével, amelyen lehetőség van egyszerre keresni a CommentIndex és ContentIndex keresési indexben is, azonban rá kellett jönnöm, hogy a jelenlegi megoldás erre nem nyújt lehetőséget. Kisebb kutatómunka után sikerült rátalálnom a Search API multi-index searches [30] modulra, amely úgy tűnt megoldja ezt a problémát, viszont a facetek ebben az 53
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
esetben működésképtelenek lettek. Nem tudtak egyszerre dolgozni a nézetben használt két indexszel, csak az egyik által szolgáltatott mezőkkel dolgoztak (pl.: a szűréssel adódó eredmények számosságánál, csak az egyiket vették figyelembe), viszont mindkét index facetjei megjelentek. Káosz állapot állt elő. A felmerült problémák és a megoldásuk Volt tehát két keresési indexem, amely a kívánt tartalmakban keresett és egy, a Views modul által létrehozott oldalam, amely végül tudott keresni mind a két indexben szövegeket, azonban a facetek nem voltak működőképesek ebben az esetben. Mondhatni szinte ez volt az egyetlen fő probléma a kereséssel kapcsolatban, amely megoldásra várt, de ez okozta a legtöbb fejtörést is. Az első lehetőség egy olyan saját keresőűrlap létrehozása volt, amely tartalmaz egy input mezőt a keresendő szöveg megadására és két rádiógomb űrlapelemet, amely kiválasztásával meg lehet adni, hogy melyik keresési indexben akarjuk keresni az adott szöveget. [3.12. ábra] Ebben a megvalósításban a két keresési index két külön oldalon foglalt volna helyett, ahol az egyik csak a tartalmakban keresett volna, a másik pedig csak a hozzászólásokban és a keresendő szöveget pedig paraméterként kapták volna meg. Mivel mindkét oldalon csak egy-egy index lett volna felhasználva így ebben az esetben a facetek is megjelentek volna oldalakon és jól működtek volna. Azonban ebben a megoldásban sem lehetett volna egyszerre megtekinteni mindkét index eredményeit, így mivel csak fél megoldással szolgált volna számunkra ez az ötlet is mellőzve lett.
3.12. ábra – A kitalált keresési form wireframeje, amely végül nem lett használva
Következő lehetséges megoldás a Search API Multi Index Facets [29] sandbox modul lett volna, amely végül nem volt működőképes. Arról, hogy mi volt vele a probléma a modullal a [Mellékletek 1.8]-as pontjában írok röviden.
54
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Mivel kész megoldást nem találtam a problémára, ezért elkezdtem saját megoldások kidolgozásán gondolkozni. Elsőként a beindexelt adatokat tartalmazó táblákat vettem szemügyre az adatbázisban és arra lettem figyelmes, hogy a CommentIndex táblájában a hozzászólásokhoz tartozó node-ok mezőinek nevei egy node_
prefixszel kerülnek bele az indexbe, ellenben a ContentIndex táblájában e nélkül
szerepelnek. Ez sok mindent megmagyarázott számomra, mivel így már érthető volt, hogy előzőekben miért jelentek meg a külön-külön a két index facetjei (noha ugyanazokat a mezőket tartalmazták mindkét indexben) illetve miért számolódtak rosszul a faceteknél látható eredményszámlálók. Ezek után azonban már volt ötletem hol és hogyan is lehetne megoldani a problémát. (Ennek az ötletnek az első verziója nem bizonyult elég flexibilisnek, ezért átdolgozásra került. Túl komplex SQL lekérdezések segítségével dolgozott, UNION-okat és JOINokat használva, így adta volna vissza az eredményeket. A kód egy rövid részlete megtekinthető a [Mellékletek 1.9] –as pontjában.) Az alap ötletet az adta, hogy a Views modul az integrációnak köszönhetően mégis tud dolgozni a két különböző indexel, úgy hogy a fulltext típusú mezőként indexelt szöveges mezőiben keresni is tud. Teszi mindezt úgy, hogy a CommentIndex táblában és a ContentIndex táblában eltérnek a mezők nevei, ahogy azt feljebb láthattuk. Ebből arra következtettem, hogy ezek szerint van rá mód, hogy olyan views szűrőt hozzunk létre, amelyben ezen eltérések ellenére is tud egyszerre dolgozni a két index. Ha már azt elérem, hogy tudok pár plusz szűrőt készíteni a keresőoldalhoz, amely az általunk kiválasztott mezők értékeire szűr (akár csak a facetek tennék), akkor az ez esetben nem működő facetekre se lesz szükségünk. Így hát először is megvizsgáltam a Search API Multi Index Searches modulban található views filter bővítmény kódját [Mellékletek 1.10], amelyben azt láttam, hogy filter úgy működik, hogy: 1. a getFulltextFields() függvényében
kilistázza az elérhető fulltext típusú
mezőként indexelt összes mezőt minden használt indexből. Teszi ezt úgy, hogy eltárolja azt is, hogy az adott index táblában milyen néven található meg az adott mező. Ezek közül aztán majd a Views modul szűrőbeállításokhoz kapcsolódó
55
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
felhasználói felületén választhatjuk ki, hogy valóban melyikeket is szeretnénk használni az elérhetőek közül. 2. Ezek után, amikor a keresésnél a filter által biztosított felfedett mezőnek értéket adunk és elindítjuk a keresést, a filter feldolgozza azt a query() függvényben aszerint, hogy az általunk kiválasztott fulltext típusú mezők milyen néven szerepelnek az adatbázisban található különböző indexekben és így a lekérdezés az összes indexben megfelelő feltételre fog lefutni. Tehát lehet olyan szűrőt készíteni a Views-hoz, amelyik tud dolgozni mindkét indexszel és már arra is van példa, hogy hogyan. A következő megoldásra váró probléma, hogy az általunk használni kívánt author, project, node type mezőkre nincs ilyen szűrőbővítmény még definiálva, tehát ehhez hasonló módon létre kell hoznom őket. Elsőként az author mezőhöz használható szűrő készült el, amely a handler_filter_author.inc
fájlban található. A filter a fulltext szűrő sémáját követve
tartalmaz egy getUserFields() függvényt, amely megpróbálja kinyerni az indexben található összes olyan mezőt, amely author mezőként szolgálhat számunkra. /** * Helper method to get an option list of all available user fields from index. */ protected function getUserFields() { $user_fields = array(); $indexes = search_api_index_load_multiple(FALSE, array('enabled' => TRUE)); foreach ($indexes as $index) { if ($index->getFields()) { $prefix = $index->machine_name . ':'; $fields = $index->getFields(); $keys = array_keys($fields); foreach ($keys as $key) { // List only the most probable "Author type" fields from the index fields. if ((isset($fields[$key]['entity_type']) && $fields[$key]['entity_type'] == 'user') || ($fields[$key]['type'] == 'integer' && strrpos($fields[$key]['name'], 'Author') !== FALSE)) { $user_fields[$prefix . $key] = $prefix . $fields[$key]['name']; } } } } return $user_fields; }
56
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Ehhez először meg kell vizsgálni, hogy az indexben mely mezők típusa „user”, majd hogy ezek integerként vannak-e letárolva, hiszen az felhasználók user id-ja (uid) szokott ez esetben az adatbázisba bekerülni. Az utolsó feltétel mondhatni kicsit egyszerűnek, de megvizsgálva a hozzászólásokat és a tartalomtípusokat láthatjuk, hogy az author mezők címkéje bizonyosan „Author”, így nézzük meg végül, hogy az index mezői közül melyeknek ez a címkéje. (Ha ezt a feltételt a későbbiekben nem lehet majd ráhúzni minden esetre, akkor nyugodtan el lehet távolítani.) A fájl további részei a Views UI által a szűrők beállításaihoz szolgáltatott általános interfész kinézetét szabják át, eltüntetve az olyan mezőket, amelyekre ebben a filterben nem lesz szükség. Ahhoz pedig, hogy a Views felismerje a frissen definiált szűrőbővítményünket
az
iq_search_views_data_alter()
iq_search.views.inc
fájlban
az
függvényben regisztrálnunk kell azt, az alábbi módon.
/** * Implements hook_views_data_alter(). */ function iq_search_views_data_alter(&$data) { $key = 'search_api_multi'; $table = &$data[$key]; // Author filter. $table['search_api_multi_author']['group'] = t('Multi Search'); $table['search_api_multi_author']['title'] = t('Author'); $table['search_api_multi_author']['help'] = t('Filter search result to author.'); $table['search_api_multi_author']['filter']['handler'] = 'SearchApiMultiHandlerFilterAuthor'; return $data; }
Ezután a szűrőknél a Multi Search kategóriát kiválasztva megtekinthetjük az új az Author szűrőnket, amelyre rákattintva az alábbi személyre szabott beállító felületet láthatjuk. [3.13 ábra]
57
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
3.13. ábra – Az Author filter beállító felülete
Az Author fields többértékű legördülő listában kiválasztott értékek a $fields = $this->options['author_fields']
változóban tárolódnak el, amelyen a filter query()
függvényében egy foreach ciklussal végigiterálva minden index megfelelő mezőjének adjuk majd át az beviteli mezőben kapott értéket. Az Author megadására egy egyszerű szöveges input mezőt használok, amely egy számot, user id-t vár értékül. (Ez kicsit sem felhasználóbarát megközelítés, de hamarosan kiderül, hogy miért is ezt a módot választottam végül.) Az ilyen egyszerű szöveges beviteli mező velejárója, hogy megfelelő validációt kell hozzá biztosítani, hiszen a felhasználó bármilyen értéket megadhat benne, amely súlyos, akár biztonsági hibákat okozhat. Ezért a szűrő kódjában az exposed_validate() függvényben definiáltam egy olyan feltételt, amely megvizsgálja, hogy az értékül kapott input olyan felhasználói azonosítót tartalmaz-e, amely valóban létezik a rendszerben. A feltételben található user_load() [10] függvény FALSE értékkel tér vissza, ha az inputként kapott azonosítójú felhasználó nem létezik a rendszerben. // Validate exposed form. public function exposed_validate(&$form, &$form_state) { parent::exposed_validate($form, $form_state); // Check if a given user id is belongs to an existing user. if (!empty($form_state['values']['search_api_multi_author']) && $form_state['values']['search_api_multi_author'] != 'All' && user_load($form_state['values']['search_api_multi_author']) === FALSE) { form_set_error('search_api_multi_author', t('Illegal choice has been detected! Please reset the form and try again!')); }
58
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben }
Miután elkészült ez a szűrőbővítmény és képes volt szűrni mindkét indexben a szerzőkre, ezek után elkészítettem a project és node type filtereket is. Ezek felépítésükben nagyban hasonlítanak a mintául szolgáló fulltext és author filterre a saját getProjectFields(), getNodeTypeFields()
és query() függvényeinkben, ezért ezeket
nem tárgyalnám külön. Inkább a szignifikánsabb eltérést mutató szűrőnként egyedi validációkat mutatnám be külön-külön röviden. A node type filterben a található validáló függvény feladata megvizsgálja, hogy a felhasználó által megadott input egy valóban létező tartalomtípus-e (pl.: project, issue, stb.) a rendszerben. Ehhez a node_type_get_types() függvény segítségével lekérdezem a rendszerben elérhető tartalomtípusokat, amelyek azonosítóját (type) egy külön tömbben eltárolom, majd megvizsgálom, hogy a kapott input érték megtaláló-e ezen tömb elemei között. Ha nem, akkor hibás a felhasználó input. // Validate exposed form. public function exposed_validate(&$form, &$form_state) { parent::exposed_validate($form, $form_state); // If the given node type doesn't exists. $node_types = array(); foreach (node_type_get_types() as $type => $data) { $node_types[] = $data->type; } // Add default field value to node types, because it's valid input too. $node_types[] = $form['search_api_multi_node_type']['#default_value']; if (!empty($form_state['values']['search_api_multi_node_type']) && !in_array($form_state['input']['search_api_multi_node_type'], $node_types)) { form_set_error('search_api_multi_node_type', t('Illegal choice has been detected! Please reset the form and try again!')); } }
A project szűrőnél pedig azt kell elsődlegesen ellenőrizni, hogy csak olyan tartalomazonosítókat (node id, nid) fogadjon el, amelyek project típusú tartalmakhoz tartoznak. Ehhez egy DBTNG [16] lekérdezést írtam, amely a node táblából lekérdezi a kapott node id-jú node típusát (ha létezik), és ha ez nem project, akkor hibával tér vissza, hiszen a kapott felhasználói input nem valós.
59
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben // Validate exposed form. public function exposed_validate(&$form, &$form_state) { parent::exposed_validate($form, $form_state); // Check if a given node id is belongs to a project node. if (!empty($form_state['values']['search_api_multi_project']) && $form_state['values']['search_api_multi_project'] != 'All') { $query = db_select('node', 'n') ->fields('n', array('type')) ->condition('nid', $form_state['values']['search_api_multi_project']); $result = $query->execute()->fetchField(); if ($result != 'project') { form_set_error('search_api_multi_project', t('Illegal choice has been detected! Please reset the form and try again!')); } } }
Elkészült a szükséges 3 szűrőbővítményem, amelyek tudtak több indexben is keresni, sőt kombinálni is lehetett őket, tehát lehetett például egyszerre szerzőre és tartalomtípusra is keresni. A megjelenítésük azonban korán sem volt felhasználóbarát, ahogy az az alábbi képen látható. [3.14 ábra]
3.14. ábra – A saját views filterek által nyújtott szöveges input mezők
Az igazság szerint a kezdetekben ezek a felfedett szűrők természetesen legördülő listaként voltak megjelenítve, amelyeket mindig feltöltöttem a megfelelő értékekkel. A probléma az volt velük, hogy mindig csak olyan értékekkel tudtam feltölteni őket, amelyek nem változnak a keresésben elérhető eredmények szerint. Például a node type szűrőt fel tudtam tölteni az összes (5 db) elérhető tartalomtípussal, azonban ennek értéke nem változott annak függvényében, hogy az adott keresés milyen eredményekkel szolgált. Ez azért okozott problémát, mert a négy létező szűrőnek köszönhetően (létező tartalmak számától függően) szinte végtelen számú olyan keresési feltételt tudtunk volna generálni, amely nem hoz eredményt. Ebben az állapotában ez a felhasználói felület se volt túl felhasználóbarát. Térjünk vissza kicsit a Facet API-hoz. A Facet API azért hasznos és jól használható, mert a „faceted search” segítségével a keresést olyan plusz szűrőkkel 60
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
egészíti ki, amelyek mindig az aktuális keresési eredménynek megfelelő újabb szűrési lehetőségeket tartalmaznak. Például az előző node type szűrős példa esetébe, ha a keresési eredmények közt kétféle tartalomtípus létezik csak, akkor csak azt a két tartalomtípust jeleni meg további szűrési lehetőségképpen. A Facet API által generált „faceted search” azonban mint feljebb említettem nem volt képes dolgozni a multiindex keresésekkel. Mint kiderült csak a felhasználói felület nem volt működő képes. Hosszas keresgélés után sikerült megtalálnom a szent grált a függvények között. Miközben azt kutattam, hogy van-e rá mód, hogy a Views által futtatott adatbázis lekérdezések közben valahogy el tudok-e érni olyan adatokat, amelyeket arra használhatok, hogy a saját szűrőimet is a keresési eredményekre jellemző adatokkal töltsem fel, rátaláltam a Facet API által visszaadott eredményekre, mindkét indexre vonatkozóan. [3.15 ábra]
3.15. ábra – A Facet API által visszaadott eredmény mindkét indexre a $view objektumban
Az eredmények lelőhelye a hook_views_post_render() függvény $view objektumában található és további hosszas kutatás után nagy bizonyossággal mondhatom, hogy csak ebben a függvényben elérhetőek ilyen formában. Ez a felfedezés hatalmas előrelépést hozott, ugyanis ha a Facet API által nyújtott „faceted search” 61
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
blokkok nem működnek megfelelően, a szükséges információk e helyen mégis elérhetőek voltak. Erre egyetlen magyarázatot tudtam csak találni, mégpedig azt, hogy a mivel Search API Multi Index Searches modul mivel képes több indexben is keresni, ezért ezt valószínűleg úgy teszi, hogy mindkét indexet külön kezeli, majd végül az eredményüket összegezve adja vissza. Mivel az indexeket adott helyet külön kezeli és az indexekre külön-külön be vannak kapcsolva a facetek, ezért azon a ponton a Facet API is lefut gond nélkül, amelyek eredményei szintén eltárolódnak összegzéskor. Az is jól látható, hogy az eredményben megjelennek a már említett adatbázisban található mezőnév prefixek is, azonban ezeket igen egyszerűen lehet eliminálni egy egyszerű összegző függvénnyel. Ennek forráskódja megtekinthető a [Mellékletek 1.11]es pontjában. A függvény először is megtisztítja a [3.15 ábra]-án látható $search_api_facets
tömb kulcsait a felesleges prefixektől, sufixektől és összegzi őket
egy közös tömbben így az összetartozó mezők eredményei egy tömbbéli kulcs alatt lesznek megtalálhatók mindkét indexből. Miután ez megtörtén szükséges még a kulcsokban található eredményekben lévő $count változók értékeinek összegzése is úgy, hogy az eredményekben lévő $filter változók értékeit vizsgálva az azonosakat összetartozóként kezeljük. Ennek köszönhetően a Facet API azon funkcióját is sikerül „lemásolni”, hogy a szűrőfeltétben található mezőnevek mellett egy zárójelben meg tudja jeleníteni, hogy az adott mezőnévre kattintva hány eredménnyel tér majd vissza a keresés. Az így elkészült tömbbel már visszatérhetünk, a későbbiekben ezt használjuk. Az általam várt megoldás következő lépése az lett volna, hogy a hook_views_post_render()
függvényimplementációmban felül is tudom majd írni a
kereső oldalnézetemben lévő saját felfedett szűrőim legördülő listáinak lehetséges értékeit. [Mellékletek 1.12] Azonban a $view objektumnak itt nem része a nem renderelt form objektum, amelyben megoldható lenne a lehetséges értékek módosítása. Ezért a $view
objektumot itt át kell adnom egy saját fake_views_exposed_search_multi_form
nevű „álűrlapnak” a drupal_get_form() függvény segítségével, és az általa visszaadott renderelt formot beillesztem a Views saját renderelt formja elé. Miért jó ez? Mi lesz ebből?
62
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Először is a saját fake_views_exposed_search_multi_form() függvényemben bekérem a kereső oldalamat generáló Views nézet felfedett szűrőjéhez tartozó eredeti űrlapnak
a
form
objektumát
hook_views_post_render()
az
$original_form
változóba,
amit
a
saját
függvényimplementációmban előzőleg nem értem el.
[Mellékletek 1.13] Ebből az objektumból később el tudom dönteni, hogy mely szűrőkhöz kell az „álűrlapban” legördülő listát készíteni illetve ezeknek a listáknak meg tudom majd adni az eredeti űrlapban található eredeti szűrők éppen aktuális értékét. A függvény további részében a három eredeti szűrőmhöz kapcsolódó legördülő listás „ál” szűrőket készítem el. Ebből most az „ál” Author szűrőhöz tartozót mutatom csak be, mivel a többi hasonlóan egy kaptafára készül, csak mások a $search_table tömb értékei, amely azt tartalmazza, hogy a Facet API által visszaadott (és általam feldolgozott) tömbben a $filter változó értéke melyik adatbázis tábla melyik elsődleges kulcsának értékét tartalmazza, illetve abból a táblából melyik mező értékét kell visszaadni a legördülő lista lehetséges értékeiként. // Check if Facet API has results for this search. if (isset($sapi_result['search_api_facets'])) { $facetapi_multi_result = _iq_search_preprocess_search_api_result($sapi_result['search_api_facet s']); // Create our custom Author filter. if (isset($original_form['search_api_multi_author']) && isset($facetapi_multi_result['author'])) { $search_table = array( 'table' => 'users', 'fields' => array('name'), 'unique_id' => 'uid', ); $author_options = _iq_search_generate_options($facetapi_multi_result['author'], $search_table); $form['search_api_multi_author'] = array( '#type' => 'select', '#title' => t('Author'), '#options' => array('All' => t('- Any -')) + $author_options, '#default_value' => isset($form_state['input']['search_api_multi_author']) ? $form_state['input']['search_api_multi_author'] : $original_form['search_api_multi_author']['#value'], ); } }
63
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Tehát a fentebbi kódrészlet szerint, ha a Facet API és az eredeti Author szűrő is be van kapcsolva , akkor készítsünk legördülő listát az „ál” Author szűrőnek az „álűrlapban”. Dolgozzuk fel az _iq_search_generate_options() függvénnyel a szerzőre vonatkozó eredményeket egy olyan tömbbé, amely megadható egy Form API-s [20] tömb legördülőlistát generáló #options kulcsának értékül. [Mellékletek 1.14] Ez a függvény nem tesz mást, mint a $search_table-ben definiált értékek szerint (amelyek itt a users táblára mutatnak) futtat egy DBTNG lekérdezést az adott táblára, a kapott feltételek szerint, majd visszaadja a kapott lekérdezés eredményét tömbként. Ezek után definiálom az „ál” Author szűrőhöz kapcsolódó legördülőlistát és lehetséges értékül adom neki a kapott eredményt, illetve az $original_form-ból érkező aktuális értéket beállítom aktuális értéknek itt is. Miután ehhez hasonlóan elkészült a többi szűrőmhöz kapcsolódó „álszűrőm” is az alábbi eredmény volt látható a kereső oldalon. [3.16 ábra]
3.16. ábra – Az eredeti szűrő űrlap és felette lévő „álűrlap”
Jól láthatóan a szöveges beviteli mezővel rendelkező eredeti szűrők feleslegesek, ezeket majd el kell rejteni, hiszen az új ál-szűrőmezők sokkal felhasználóbarátabbak. Előtte azonban kell egy rövid jQuery script, amely egy ál-szűrőmező értékének változása esetén átadja annak értékét az annak megfelelő eredeti szűrőmezőnek. Ez azért fontos mert a Search gombra kattintva majd az eredeti űrlap lesz elküldve, nem pedig az ál, amelynek ezért nincs is submit handler definiálva. Ezt a problémát megoldó jQuery szkript kódja a [Mellékletek 1.15]-ös pontjában látható. Az eredeti szűrőmezők elrejtése pedig a iq_search_form_views_exposed_form_alter() függvényben valósul meg, amely egy hook_form_FORM_ID_alter() implementáció és az eredeti szűrőmezők típusát hidden-re állítva elrejti azokat a felhasználók elől (csak a HTML kimenetben fognak megjelenni). [Mellékletek 1.16]
64
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Mindezek után elkészült a végleges felfedett szűrő, amely kihasználja a Facet API által nyújtott adatokat saját felhasználói felületén és tud keresni mindkét keresőindexben.
3.2.4-11. ábra – A kész szűrő végleges verziója a kereső oldalon
A problémák sora azonban itt sajnos nem ért véget. A kereső oldal most már rendesen működött a szűrőkkel együtt. Ennek köszönhetően sikerült észrevenni a Search API egy hibáját, ami pontosabban egy hiányzó funkcionalitás volt. A Search API a tartalmak (node-ok) beindexelésekor képes eltárolni az azokhoz kapcsolódó node access információkat is az indexbe, amelynek köszönhetően a keresőn keresztül is csak azokhoz a tartalmakhoz férhet hozzá a felhasználó, amelyhez egyébként is lenne jogosultsága. Ez a funkció azonban a hozzászólások esetében hiányzott a Search APIból és így minden felhasználó hozzáférhetett minden hozzászóláshoz a kereső oldalon keresztül. El se lehet mondani ez mekkora hibának számított az Openspace-ben, amelynek egyik legfőbb szempontja a megfelelő hozzáférés szabályozás. Ennek a hibának köszönhetően azok is hozzáférhetnek különböző projektek ügyeiben található hozzászólásokhoz, akik nem is voltak tagjai az adott projekteknek. Mivel hasonló hibajelentést, feature requestet nem találtam a modul issue queujában, ezért saját kezembe vettem a hiba javítását. Ehhez alapul vettem a Search API Node access ellenőrzését tartalmazó callback_node_access.inc fájl tartalmát, amelyet tüzetesebben megvizsgálva rájöttem, hogy csupán kisebb módosításokkal egyszerűen alkalmazható lesz majd a hozzászólásokra is. Miután sikerült kifejlesztenem a funkciót publikáltam a patch-et egy új issue-ban [46], mivel úgy gondoltam másnak is szüksége lehet erre a későbbiekben. Ezek után sikerült felvennem a kapcsolatot a Search API modul fejlesztőjével, akivel együttműködve sikerült még egyszerűbbé tenni a megoldásomat, amely aztán a Search API modul része is lett. [46]
65
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
A végső megoldáshoz kapcsolódó patch megtekinthető a [Mellékletek 1.17]–es pontjában. A megoldás tartalmaz először is egy új fájlt callback_comment_access.inc néven.
Ebben
található
SearchApiAlterNodeAccess
a
SearchApiAlterCommentAccess
osztály,
amely
osztályt bővíti ki, így egyszerűen örökölve a két funkció
hasonlóságait és lehetőséget adva a különbségek felülírására. Mivel a fájl osztály tartalmaz, ezért a search_api.info fájlhoz is hozzá kellett a files[] tömb egy új elemeként. Az
első
supportsIndex()
eltérés
a
SearchApiAlterNodeAccess
osztályhoz
képest
a
függvényben található, ahol a $index->getEntityType() értéket felül
kell definiálni „comment”-re, hiszen ez az új funkció a hozzászólásokat tartalmazó indexek esetében lesz csak használható. A következő eltérést a getNode() függvény tartalmazza, amely paraméterül egy comment objektumot fog kapni nem pedig node-ot, tehát az ahhoz tartozó node objektumot a node_load($item->nid) hívással tudjuk majd elérni. Ebből a node objektumból lehet kinyerni a szükséges node_access információt azaz, hogy melyik felhasználó férhet hozzá az adott tartalmakhoz kapcsolódó hozzászólásokhoz. (Hiszen csak akkor férhet hozzá a hozzászólásokhoz a felhasználó, ha magához a szülőtartalomhoz is van hozzáférése.) Valamint felüldefiniálásra került a configurationFormSubmit()
függvény is, hiszen nem az ősosztályét szeretnék
beállítások tárolására használni. Ezek után, hogy elérhető legyen saját Access check névra hallgató funkciónk a Search
API
hozzászólás
indexekhez
kapcsolódó
adminisztrációs
felületén
regisztrálnunk kell azt a search_api_search_api_alter_callback_info() függvényben. Most már csak a Search API Node access funkciója által eddig is használt függvényeket kell módosítani, hogy együtt tudjanak működni az új (hozzászólás) Access check funkcióval is. A _search_api_query_add_node_access() függvényben be kell vezetni egy új $type
paramétert, amely megmondja, hogy node-ról vagy commentről van szó. Ennek
segítségével már könnyen megadhatóak olyan új feltételek is, mint például, ha a felhasználónak nincs joga megtekinteni a nem közzétett tartalmakat, akkor az azokhoz tartozó nem közzétett hozzászólásokat se láthassa. 66
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Ezekkel
az
apróbb módosításoknak és
kiegészítéséknek köszönhetően
megoldódott az Openspace keresőrendszerének utolsó hibája is. Összefoglalás Végül sikerült a keresés funkcióban is megvalósítani mindent az előzetes elvárásainknak megfelelően. Ehhez ismét számos akadályt kellett leküzdeni, amelynek köszönhetően újabb tudással gyarapodtam. Habár a funkció megvalósításának bizonyos pontjaiban már-már úgy tűnt, hogy le kell mondanunk a Facet API által szolgáltatott keresőbővítményről, mégis sikerült kisebb kompromisszumok árán implementálni ezt is a rendszerbe (amely csak a felhasználói interfészt érintette). Mindennek köszönhetően egy jól használható kereső funkció is része lett az Openspace-nek.
67
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
4. ÖSSZEFOGLALÁS 4.1.
AZ ELÉRT EREDMÉNYEK
A szakdolgozat végén a hozzá kapcsolódó funkciók kapcsán nagy örömmel mondhatom el, hogy a kezdeti célkitűzésekből sikerült minden rám bízott funkciót megvalósítanom. Az idáig vezető út tele volt rengeteg nem számolt akadállyal és olyan helyzettel, amikor teljes mértékben az önállóságomra és problémamegoldó képességemre kellett hagyatkoznom. Ennek köszönhetően úgy érzem a tudásom még tovább gyarapodott a Drupallal kapcsolatban. Hála az Openspace fejlesztésének és a felmerült problémáknak a különböző modulok kapcsán számos javító patch-em közzétételre került a drupal,org-on, amelyek közül talán a legbüszkébb az utóbb említett Search API-hoz készített (hozzászólás) Access checkre vagyok. Ennek köszönhetően még inkább részese lehettem a nagy Drupal közösségnek.2 Szintén fantasztikus érzés, hogy egy ilyen rendszer egyik fejlesztője lehettem, mint az Openspace, amely ennyire összetett és ennyi újdonságnak számító megvalósítást hordoz magában. Munkánk gyümölcse jelenleg még csak belső használatban van a cégnél. Az első tesztelői mi magunk szerettünk volna lenni, mielőtt nagy nyilvánosság elé tárjuk az Openspace. Ennek köszönhetőn is már számos javítás és kiegészítés került azóta is a rendszerbe. Hamarosan eléri azt az állapotát, amikor bátran merjük a nagyközönség elé tárni, hogy ők is véleményezzék a terméket.
2
Emellett még készítettem Drupal 6 modul portot is Drupal 7-re, amely végül nem lett része a rendszernek egyelőre, mégis ezt is igazi kihivásként éltem meg. (comment_revision modul, amely kódja megtalálható a CD-n és beküldésre is került a kapcsolódó issuehoz: https://drupal.org/node/1033290)
68
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
4.2.
BŐVÍTÉSI LEHETŐSÉGEK
Az Openspace rengeteg további lehetőséget hordoz magában. Ahogy a kezdetek elején csak egy egyszerű ügyekezelőnek indult, azóta már a kanban bővítménynek köszönhetően agilis fejlesztés támogatására is alkalmas. Hála annak, hogy Drupalra építetett, a rendszer megőrizte modularitását így további kiegészítések készítése is igen egyszerű lesz majd a jövőben. Például már most folyamatosan szó van arról cégen belül, hogy a Kanban [37] mellett a Scrumot [47] is támogatnia kellene majd a rendszernek. Nagy valószínűséggel ha majd a rendszer a nyilvánosság elé kerül, akkor további feature requesteket is fogunk kapni, amelyekből a legjobbakat igyekszünk majd a rendszer részévé tenni.
69
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
IRODALOMJEGYZÉK 1.
20 APIs in 20 Days: The Hooks API and Custom Modules http://www.trellon.com/content/blog/hooks-api
2.
Apache Solr http://lucene.apache.org/solr/
3.
Drupal - API Drupal http://api.drupal.org/api/group/hooks
4.
Drupal - API Drupal - function drupal_set_message https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/drupal_set_message/7
5.
Drupal - API Drupal - function hook_install https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_install/7
6.
Drupal - API Drupal - function hook_install_tasks() https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_install_tasks/7
7.
Drupal - API Drupal - function hook_node_access https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_access/7
8.
Drupal - API Drupal - function hook_node_info https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_info/7
9.
Drupal - API Drupal - function module_invoke_all https://api.drupal.org/api/drupal/includes%21module.inc/function/module_invoke_all/7
10. Drupal - API Drupal - function user_load https://api.drupal.org/api/drupal/modules!user!user.module/function/user_load/7 11. Drupal - Batch API http://api.drupal.org/api/drupal/includes--form.inc/group/batch/7 12. Drupal - Coding standards https://drupal.org/coding-standards 13. Drupal - Creating Drupal 7.x modules https://drupal.org/developing/modules/7 14. Drupal - Creating modules - a tutorial: Drupal 7.x https://drupal.org/node/1074360 15. Drupal - Default Content module https://drupal.org/project/defaultcontent 16. Drupal - Database API (DBTNG) https://drupal.org/developing/api/database 17. Drupal - Devel module
70
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
https://drupal.org/project/DEVEL 18. Drupal - Facet API module https://drupal.org/project/facetapi 19. Drupal - Features module https://drupal.org/project/features 20. Drupal - Form API http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html 21. Drupal - Install Profile API https://drupal.org/node/908088 22. Drupal - Message notify module https://drupal.org/project/message_notify 23. Drupal - Messaging module https://drupal.org/project/messaging 24. Drupal - Node export module https://drupal.org/project/node_export 25. Drupal - Notifications module https://drupal.org/project/notifications 26. Drupal - Organic Groups module https://drupal.org/project/og 27. Drupal - Search API module https://drupal.org/project/search_api 28. Drupal - Search API Database module https://drupal.org/project/search_api_db 29. Drupal - Search API Multi Index Facets module https://drupal.org/sandbox/larowlan/1678766 30. Drupal - Search API multi-index searches module https://drupal.org/project/search_api_multi 31. Drupal - Search API Solr search module https://drupal.org/project/search_api_solr 32. Drupal - Strongarm module https://drupal.org/project/strongarm 33. Drupal - UUID features module https://drupal.org/project/uuid_features 34. Drupal - UUID module https://drupal.org/project/uuid
71
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
35. Drupal - Views module https://drupal.org/project/views 36. Isaac Sukin - How to Write a Drupal 7 Installation Profile http://www.isaacsukin.com/news/2011/01/10/how-write-drupal-7-installation-profile 37. Kanban (developement) http://en.wikipedia.org/wiki/Kanban_%28development%29 38. A MySQL 5.5+ szerver InnoDB engine beállításáról http://neferkat.blog.hu/2012/11/23/a_mysql_szerver_innodb_engine_beallitasarol 39. Oldalgazda: Tartalomkezelő rendszer http://webni.innen.hu/Tartalomkezel_c5_91Rendszer 40. Open Atrium http://openatrium.com/ 41. Organic Groups Notifications https://drupal.org/project/og_notifications 42. Responsible maintainers http://buytaert.net/responsible-maintainers 43. Responsible module maintainership http://www.palantir.net/blog/responsible-module-maintainership 44. Performance tuning tips for Drupal 7 testing https://qa.drupal.org/performance-tuning-tips-for-D7 45. Saját patch - Error after enabling this module 7.x-2.x-dev. Undefined variable: gid https://drupal.org/node/1671316 https://drupal.org/comment/7954015#comment-7954015 46. Saját patch - Node access check missing from comment indexes https://drupal.org/node/2118589 http://drupalcode.org/project/search_api.git/commit/2a4b08d 47. Scrum (software developement) http://en.wikipedia.org/wiki/Scrum_%28software_development%29 48. Verziókezelő rendszer http://wiki.hup.hu/index.php/Verzi%C3%B3kezel%C5%91_rendszer 49. Wikipedia - Bug Tracking System http://en.wikipedia.org/wiki/Bug_tracking_system 50. Wikipedia - Content management system http://en.wikipedia.org/wiki/Content_management_system 51. Wikipedia - Content management systems (CMS) http://en.wikipedia.org/wiki/Web_application_framework#Content_management_systems_.28C MS.29
72
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
52. Wikipedia - Drupal http://en.wikipedia.org/wiki/Drupal 53. Wikipedia - Drupal http://hu.wikipedia.org/wiki/Drupal 54. Wikipedia - GIT http://hu.wikipedia.org/wiki/Git 55. Wikipedia - GIT software http://en.wikipedia.org/wiki/Git_%28software%29 56. Wikipedia - GNU General Public License http://hu.wikipedia.org/wiki/GNU_General_Public_License 57. Wikipedia - Issue Tracking System http://en.wikipedia.org/wiki/Issue_tracking_system 58. Wikipedia – Issue Tracking systems (comparison) http://en.wikipedia.org/wiki/Comparison_of_issue-tracking_systems 59. Wikipedia - Tartalomkezelő rendszerek http://hu.wikipedia.org/wiki/Tartalomkezel%C5%91_rendszerek 60. Wikipedia - Verziókezelés http://hu.wikipedia.org/wiki/Verzi%C3%B3kezel%C3%A9s
73
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
NYILATKOZAT Alulírott Biczó Dezső, gazdaságinformatikus Bsc. szakos hallgató, kijelentem, hogy a dolgozatomat a Szegedi Tudományegyetem, Informatikai Tanszékcsoport Szoftverfejlesztés
Tanszékén
készítettem,
gazdaságinformatikus
Bsc.
diploma
megszerzése érdekében. Kijelentem, hogy a dolgozatot más szakon korábban nem védtem meg, saját munkám eredménye, és csak a hivatkozott forrásokat (szakirodalom, eszközök, stb.) használtam fel. Tudomásul veszem, hogy szakdolgozatomat a Szegedi Tudományegyetem Informatikai Tanszékcsoport könyvtárában, a helyben olvasható könyvek között helyezik el.
2013. december 4. Aláírás
74
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
KÖSZÖNETNYILVÁNÍTÁS Szakdolgozatom végén szeretnék köszönetet nyilvánítani a KYbestnek, akiknek a szakdolgozatom témáját köszönhetem. Kohán Péternek a cég vezetőjének és Csécsy Lászlónak, aki azon kívül, hogy a cég főfejlesztője, de évek óta mentorom és most a szakdolgozatom témavezetője is volt.
75
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
MELLÉKLETEK 1.1.
IQ_PROFILE_INSTALL_EXTRA_FEATURES()
/** * Install step callback to install selected extra features. * * @param $install_state * An array of information about the current installation state. * * @return array * An array of batch operations. */ function iq_profile_install_extra_features() { $batch = array( 'title' => st('Enabling and configuring extra features'), 'operations' => array(), ); // Install Kanban Developement Tools if selected. if (isset($_POST['kanban_development_tools']) && $_POST['kanban_development_tools']) { $features = array( 'iq_user_story', ); foreach ($features as $feature) { $batch['operations'][] = array( 'features_install_modules', array(array($feature)) ); } // Features needs to be reverted. $batch['operations'][] = array('features_revert', array()); } // Add test users if selected. if (isset($_POST['test_users']) && $_POST['test_users']) { $users = array( 'developer-hu-1', 'developer-hu-2', 'developer-en-1', 'developer-en-2', 'customer-hu-1', 'customer-en-1', 'pmanager-hu-1', 'pmanager-en-1', ); foreach ($users as $user) { $fields = array( 'name' => $user, 'mail' => $user . '@kybest.hu', 'pass' => 'teszt', 'status' => 1, // FIXME: Only create non-English users if the appropriate language has // FIXME: been selected beforehand. 'language' => strrpos($user, 'en') !== FALSE ? 'en' : 'hu', 'roles' => array(
76
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben DRUPAL_AUTHENTICATED_RID => 'authenticated user', ), ); if (strrpos($user, 'pmanager') !== FALSE) { $fields['roles'][] = 'admin'; } user_save('', $fields); } } // Enable debug cruft if selected. if (isset($_POST['issueq_debug']) && $_POST['issueq_debug']) { // Catch all mails. variable_set('mail_system', array('default-system' => 'DevelMailLog')); // Enable Switch user block in first sidebar. db_insert('block') ->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')) ->values(array( 'module' => 'devel', 'delta' => 'switch_user', 'theme' => variable_get('theme_default', 'iq_garland'), 'status' => BLOCK_CUSTOM_ENABLED, 'weight' => 3, 'region' => 'sidebar_first', 'pages' => '', 'cache' => DRUPAL_NO_CACHE, )) ->execute(); // Enable development modules. $modules = array( 'devel', 'views_ui', 'field_ui', 'contextual', 'mailsystem', ); foreach ($modules as $module) { $batch['operations'][] = array( 'module_enable', array(array($module)) ); } // Reverting features should not harm. $batch['operations'][] = array('features_revert', array()); } return $batch; }
77
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.2.
OG NODE - PROJECT PERMISSIONS TÁBLÁZAT
78
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
79
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
80
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.3.
PERMISSION MAZE
Felhasználói szerepkörök Funkció Site Auth.user admin Felhasználó létrehozása az oldalon x Felhasználó törlése x Saját felhasználó szerkesztése x x Bármely felhasználó szerkesztése x Felhasználó hozzáadása projekthez x Felhasználó eltávolítása projektből x Projekt létrehozása x Projekt szerkesztése x Projekt törlése x Projektek listájának megtekintése (minden projekt) x Projektek listájának megtekintése (amelyeknek tagja) x Bármely projekt megtekintése x Projekt megtekintése (amelyeknek tagja) x Összes ügy listájának megtekintése x Ügyek listájának megtekintése (amely projektnek tagja) x Ügyek listájának megtekintése (amelyek hozzárendeltek) x Bármely ügy megtekintése x Ügy megtekintése (amely projektnek tagja) x Hozzárendelt ügy megtekintése x Ügy hozzáadása bármely projekthez x Ügy hozzáadása (amely projektnek tagja) x Bármely ügy szerkesztése x Ügy szerkesztése (amely projektnek tagja) x Saját (magam által beküldött) ügy szerkesztése x Saját (felelősként hozzám rendelt) ügy szerkesztése x Bármely ügy törlése x Ügy törlése (amely projektnek tagja) x Saját (magam által beküldött) ügy törlése x Saját (felelősként hozzám rendelt) ügy törlése x Bármely hír megtekintése x Saját projektbeli hír megtekintése x Olyan projektbeli hír megtekintése, melynek tagja vagyok x Hír hozzáadása bármely projekthez x Saját projekthez hír hozzáadása x Hír hozzáadása olyan projekthez, amelynek tagja vagyok x
OG szerepkörök Member Fejlesztő Ügyfél
OG Admin
-
-
-
x x
-
-
-
x x
-
-
-
-
x -
x -
x -
x -
x -
x -
x -
x -
x
x
x
x
x -
x -
x -
x -
x x -
x x -
x x -
x x -
-
x
x
x
-
-
-
x
-
x
x
x
-
-
-
x
-
-
-
x
-
-
-
x
-
-
-
x
-
x
-
x
x
x
x
x
-
-
-
x
x
x
x
x
81
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
Bármely hír szerkesztése Saját projekthez kapcsolódó hír szerkesztése Saját (magam által beküldött) hír szerkesztése Bármely hír törlése Saját projekthez kapcsolódó hír törlése Saját (magam által beküldött) hír törlése Bármely dokumetáció megtekintése Saját projektbeli dokumentáció megtekintése Olyan projektbeli dokumentáció megtekintése, melynek tagja vagyok Dokumentáció hozzáadása bármely projekthez Saját projekthez dokumentáció hozzáadása Dokumentáció hozzáadása olyan projekthez, amelynek tagja vagyok Bármely dokumetáció kiemeltnek jelölése Saját dokumentáció kiemeltnek jelölése Saját projekthez kapcsolódó dokumentáció kiemeltnek jelölése Bármely dokumentáció kiemeltségének megszüntetése Saját dokumentáció kiemeltségének megszüntetése Saját projekthez kapcsolódó dokumetáció kiemeltnek jelölésének megszüntetése Bármely dokumentáció hozzáadása létező könyvhöz bármely projektben Bármely dokumentáció hozzáadása létező könyvhöz saját projektben Bármely dokumentáció hozzáadása létező könyvhöz, abban a projektben, amelynek tagja vagyok Saját dokumentáció hozzáadása letező könyvhöz bármely projektben Saját dokumentáció hozzáadása letező könyvhöz saját projektben Saját dokumentáció hozzáadása letező könyvhöz, abban a projektben, amelynek tagja vagyok Bármely dokumentáció hozzáadása új könyhöz bármely projektben Bármely dokumentáció hozzáadása új könyvhöz saját projektben Bármely dokumentáció hozzáadása új könyhöz, abban a projektben, amelynek tagja vagyok Saját dokumentáció hozzáadása új könyhöz, abban a projektben, amelynek tagja vagyok
x x
-
-
-
x
x x
x
x
x
x
x
-
-
-
x
x x
x
x
x
x
x
-
-
-
x
x
x
x
x
x
x
-
-
-
x
x
-
x
-
x
x
-
x
-
x
x
-
-
-
x
x
-
-
-
x
x
-
-
-
x
x
-
-
-
x
x
-
-
-
x
x
-
-
-
x
x
-
x
-
x
x
-
-
-
x
x
-
-
-
x
x
-
x
-
x
x
x
x
x
x
x
82
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben Bármely dokumentáció eltávolítása könyvből bármely projektben Bármely dokumentáció eltávolítása könyvből saját projektben Bármely dokumentáció eltávolítása könyből abban a projektben, amelynek tagja vagyok Saját dokumetánció eltávolítása könyből bármely projektből Saját dokumentáció eltávolítása könyből saját projektben Saját dokumentáció eltávolítása könyvből abban a projektben, amelynek tagja vagyok Bármely dokumentáció szerkesztése Saját projekthez kapcsolódó dokumentáció szerkesztése Saját (magam által beküldött) dokumentáció szerkesztése Bármely dokumentáció törlése Saját projekthez kapcsolódó dokumentáció törlése Saját (magam által beküldött) dokumentáció törlése Komment beküldése bármely ügyhöz Komment beküldése olyan projekt ügyeihez, amelynek tagja Komment beküldése olyan ügyhöz, melynek felelőse Komment beküldése olyan ügyhöz, melynek szerzője Komment törlése bármely ügyből Komment törlése olyan projekt ügyeiből, amelynek tagja Komment törlése olyan ügyből, melynek felelőse Komment törlése olyan ügyből, melynek szerzője Bármely komment szerkesztése Saját komment szerkesztése Komment szerkesztése olyan projektben, melynek tagja Komment szerkesztése olyan ügyben, melynek felelőse Komment változásainak megtekeintése Saját komment változásainak megtekintése Komment változatának visszaállítása Saját komment válozatának visszaállítáa Komment változatának törlése Saját komment válozatának törlése Comment timer használata komment készítés közben
x x
-
-
-
x
x
-
-
-
x
x
-
-
-
-
x
-
-
-
x
x
-
x
-
x
x
-
-
-
x
x x
-
x
-
x
x
-
-
-
x
x
-
x
-
x
x
x
x
x
x
x
x
x
x
x
x x
x
x
x
x
x
-
-
-
x
x
-
-
-
x
x x x
-
-
-
x
-
-
-
x
x
-
-
-
x
x
-
-
-
x
-
x
-
-
-
-
x
x
-
x
-
-
-
-
-
x x x
-
-
-
-
-
x
x
x
83
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben Comment timer megtekintése bármely kommentnél Comment timer megtekintése olyan projekt ügyeinek kommentjeinél, amelynek tagja Comment timer megtekintése olyan ügy kommentjeinél, amelynek felelőse Comment timer megtekintése olyan ügy kommentjeinél, amelynek szerzője Comment timer szerkesztése bármely komment szerkesztésekor Comment timer szerkesztése olyan projekt ügyeinek kommentjének szerkesztésekor, amelynek tagja Comment timer szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek felelőse Comment timer szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek szerzője Comment body használata komment készítés közben Comment body megtekintése bármely kommentnél Comment body megtekintése olyan projekt ügyeinek kommentjeinél, amelynek tagja Comment body megtekintése olyan ügy kommentjeinél, amelynek felelőse Comment body megtekintése olyan ügy kommentjeinél, amelynek szerzője Comment body szerkesztése bármely komment szerkesztésekor Comment body szerkesztése olyan projekt ügyeinek kommentjének szerkesztésekor, amelynek tagja Comment body szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek felelőse Comment body szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek szerzője Comment subject használata komment készítés közben Comment subject megtekintése bármely kommentnél Comment subject megtekintése olyan projekt ügyeinek kommentjeinél, amelynek tagja Comment subject megtekintése olyan ügy kommentjeinél, amelynek felelőse Comment subject megtekintése olyan ügy kommentjeinél, amelynek szerzője
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
x
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
84
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben Comment subject módosítása bármely komment beküldésekor Comment subject módosítása olyan projekt ügyeinek kommentjének beküldésekor, amelynek tagja Comment subject módosítása olyan ügy kommentjének beküldésekor, amelynek felelőse Comment subject módosítása olyan ügy kommentjének beküldésekor, amelynek szerzője Comment subject szerkesztése bármely komment szerkesztésekor Comment subject szerkesztése olyan projekt ügyeinek kommentjének szerkesztésekor, amelynek tagja Comment subject szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek felelőse Comment subject szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek szerzője Comment attachment megtekintése bármely kommentnél Comment attachment megtekintése olyan projekt ügyeinek kommentjeinél, amelynek tagja Comment attachment megtekintése olyan ügy kommentjeinél, amelynek felelőse Comment attachment megtekintése olyan ügy kommentjeinél, amelynek szerzője Comment attachment hozzáadása bármely komment beküldésekor Comment attachment hozzáadása olyan projekt ügyeinek kommentjének beküldésekor, amelynek tagja Comment attachment hozzáadása olyan ügy kommentjének beküldésekor, amelynek felelőse Comment attachment hozzáadása olyan ügy kommentjének beküldésekor, amelynek szerzője Comment attachement szerkesztése komment módosításakor bármely ügynél Comment attachment szerkesztése olyan projekt ügyeinek kommentjének szerkesztésekor, amelynek tagja Comment attachment szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek felelőse Comment attachment szerkesztése olyan ügy kommentjének szerkesztésekor, amelynek szerzője
-
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
x
x
x
85
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben Felelős megtekintése bármely ügynél Felelős megtekintése olyan projekt ügyénél, amelynek tagja Felelős megtekintése olyan ügynél, amelynek felelőse Felelős megtekintése olyan ügynél, amelynek szerzője Felelős módosítása komment beküldésekor bármely ügynél Felelős módosítása komment beküldésekor olyan projekt ügyénél, amelynek tagja Felelős módosítása komment beküldésekor olyan ügynél, amelynek felelőse Felelős módosítása komment beküldésekor olyan ügynél, amelynek szerzője Felelős módosítása komment szerkesztésekor bármely ügynél Felelős módosítása komment szerkesztésekor olyan projekt ügyénél, amelynek tagja Felelős módosítása komment szerkesztésekor olyan ügynél, amelynek felelőse Felelős módosítása komment szerkesztésekor olyan ügynél, amelynek szerzője Hozzárendelés megtekintése bármely ügynél Hozzárendelés megtekintése olyan projekt ügyénél, amelynek tagja Hozzárendelés megtekintése olyan ügynél, amelynek felelőse Hozzárendelés megtekintése olyan ügynél, amelynek szerzője Hozzárendelés módosítása komment beküldésekor bármely ügynél Hozzárendelés módosítása komment beküldésekor olyan projekt ügyénél, amelynek tagja Hozzárendelés módosítása komment beküldésekor olyan ügynél, amely projektnek felelőse Hozzárendelés módosítása komment beküldésekor olyan ügynél, amelynek szerzője Hozzárendelés módosítása komment szerkesztésekor bármely ügynél Hozzárendelés módosítása komment szerkesztésekor olyan projekt ügyénél, amelynek felelőse Hozzárendelés módosítása komment szerkesztésekor olyan ügynél, amelynek projektnek tagja
-
-
-
-
-
-
x x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
86
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben Hozzárendelés módosítása komment szerkesztésekor olyan ügynél, amelynek szerzője Cimkék megtekintése bármely ügynél Cimkék megtekintése olyan projekt ügyénél, amelynek tagja Cimkék megtekintése olyan ügynél, amelynek felelőse Cimkék megtekintése olyan ügynél, amelynek szerzője Cimkék módosítása komment beküldésekor bármely ügynél Cimkék módosítása komment beküldésekor olyan projekt ügyénél, amelynek tagja Cimkék módosítása komment beküldésekor olyan ügynél, amelynek felelőse Cimkék módosítása komment beküldésekor olyan ügynél, amelynek szerzője Cimkék módosítása komment szerkesztésekor bármely ügynél Cimkék módosítása komment szerkesztésekor olyan projekt ügyénél, amelynek tagja Cimkék módosítása komment szerkesztésekor olyan ügynél, amelynek felelőse Cimkék módosítása komment szerkesztésekor olyan ügynél, amelynek szerzője Prioritás megtekintése bármely ügynél Prioritás megtekintése olyan projekt ügyénél, amelynek tagja Prioritás megtekintése olyan ügynél, amelynek Prioritáse Prioritás megtekintése olyan ügynél, amelynek szerzője Prioritás módosítása komment beküldésekor bármely ügynél Prioritás módosítása komment beküldésekor olyan projekt ügyénél, amelynek tagja Prioritás módosítása komment beküldésekor olyan ügynél, amelynek felelőse Prioritás módosítása komment beküldésekor olyan ügynél, amelynek szerzője Prioritás módosítása komment szerkesztésekor bármely ügynél Prioritás módosítása komment szerkesztésekor olyan projekt ügyénél, amelynek tagja Prioritás módosítása komment szerkesztésekor olyan ügynél,
x -
-
-
-
-
-
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
x
87
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben amelynek felelőse Prioritás módosítása komment szerkesztésekor olyan ügynél, amelynek szerzője Státusz megtekintése bármely ügynél Státusz megtekintése olyan projekt ügyénél, amelynek tagja Státusz megtekintése olyan ügynél, amelynek Státusze Státusz megtekintése olyan ügynél, amelynek szerzője Státusz módosítása komment beküldésekor bármely ügynél Státusz módosítása komment beküldésekor olyan projekt ügyénél, amelynek tagja Státusz módosítása komment beküldésekor olyan ügynél, amelynek felelőse Státusz módosítása komment beküldésekor olyan ügynél, amelynek szerzője Státusz módosítása komment szerkesztésekor bármely ügynél Státusz módosítása komment szerkesztésekor olyan projekt ügyénél, amelynek tagja Státusz módosítása komment szerkesztésekor olyan ügynél, amelynek felelőse Státusz módosítása komment szerkesztésekor olyan ügynél, amelynek szerzője Típus megtekintése bármely ügynél Típus megtekintése olyan projekt ügyénél, amelynek tagja Típus megtekintése olyan ügynél, amelynek felelőse Típus megtekintése olyan ügynél, amelynek szerzője Típus módosítása komment beküldésekor bármely ügynél Típus módosítása komment beküldésekor olyan projekt ügyénél, amelynek tagja Típus módosítása komment beküldésekor olyan ügynél, amelynek felelőse Típus módosítása komment beküldésekor olyan ügynél, amelynek szerzője Típus módosítása komment szerkesztésekor bármely ügynél Típus módosítása komment szerkesztésekor olyan projekt ügyénél, amelynek tagja Típus módosítása komment szerkesztésekor olyan ügynél,
x -
-
-
-
-
-
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x x
-
-
-
-
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
-
-
-
-
x
-
-
-
-
x
x
x
x
x
88
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben amelynek felelőse Típus módosítása komment szerkesztésekor olyan ügynél, amelynek szerzője Saját feliratkozások szerkesztése Bárkinek a feliratkozásainak szerkesztése Feliratkozás adott projektre Feliratkozás adott ügyre Feliratkozás adott postra Feliratkozás projekt tartalomtípusra Feliratkozás ügy tartalomtípusra Feliratkozás projekt összes kapcsolódó tartalmára Feliratkozás az ügy tartalomtípusra csak az adott projektben
x
x x
x x x x x
x x x x x x
x
x
x
x
-
-
-
-
89
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.4.
PERMISSION AXES
KIINDULÓ Tengelyek Jogosultságok száma 1. CRUD szerint 4 1.1 Beküldés 1.2 Megtekintés 1.3 Szerkesztés 1.4 Törlés 2. Beküldő szerint 2 2.1 Saját tartalom 2.2 Más tartalma 4. Felelős szerint (Issue és User story CT-nél) 2 4.1 Felelőse vagyok 4.2 Nem vagyok felelőse 5. OG szerepkör szerint 5 5.0 Nem tagja a csoportnak 5.1 Member 5.2 Developer 5.3 Customer 5.4 OG admin 6. Site szerepkör szerint 5 6.1 Anonymous 6.2 Authenticated 6.3 Developer 6.4 Customer 6.5 Site admin
VÉGLEGES
Tengelyek 1. CRUD szerint 1.1 Beküldés 1.2 Megtekintés 1.3 Szerkesztés 1.4 Törlés 2. OG szerepkör szerint 2.0 Nem tagja a csoportnak/projektnek 2.1 Member 2.2 Developer 2.3 Customer 2.4 OG admin 3. Site szerepkör szerint 3.1 Anonymous (semmihez soha nem fér hozzá) 3.2 Authenticated 3.3 Site admin
Jogosultságok száma 4
5
1
90
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.5.
OG NOTIFICATIONS PATCH
--- og_notifications.inc (date 1381425543000) +++ og_notifications.inc (revision ) @@ -23,12 +23,18 @@ * Set group. */ public function set_group($node) { if (og_get_group_type('node', $node->type)) { + if (og_get_group_type('node', $node->type, 'group')) { $gid = $node->nid; } elseif (og_get_group_type('node', $node->type, 'group content')) { $gid = array_pop(array_pop(og_get_entity_groups('node', $node))); + $node_groups = og_get_entity_groups('node', $node); + if (in_array('node', $node_groups)) { + $gid = array_pop(array_pop($node_groups)); } + } + else { + $gid = array_pop($node_groups); + } + } $this->get_field('node:gid')->set_value($gid); return $this; } @@ -114,13 +120,26 @@ * Get value from node or from node type */ public function object_value($object) { + // Check if it is a node, not a comment for example. + if ($object->type == 'node') { $node = node_load($object->get_value()); + $node = node_load($object->get_value()); if (og_get_group_type('node', $node->type)) { + // It is a group node? + if (og_get_group_type('node', $node->type, 'group')) { return $node->nid; } + return $node->nid; + } + // So this is a group content probably. elseif (og_get_group_type('node', $node->type, 'group content')) { + elseif (og_get_group_type('node', $node->type, 'group content')) { return array_pop(array_pop(og_get_entity_groups('node', $node))); + $node_groups = og_get_entity_groups('node', $node); + // Get the ID of group. + if (array_key_exists('node', $node_groups)) { + // array_pop() in array_pop() causes php errors, so we need a temp variable. + $temp = array_pop($node_groups); + return array_pop($temp); } + } + else {
91
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben + return array_pop($node_groups); - } -} + } + } + } + } +}
92
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.6.
IQ_NOTIFICATIONS.CLASSES.INC
TRUE, '#markup' => t('Greetings [user:name], here are some details:', array(), $options), ); } /** * Default texts for this template, may need token replacement. */ function default_text($type, $options) { $template_objects = $this->get_objects(); switch ($type) { case 'subject': $subject = t('[[node:og-group-ref]] Update for [node:typename]: [node:title] #[node:nid]', array(), $options); // Project CT specific subject. if (isset($template_objects['node']) && $template_objects['node']->type == 'project') { $subject = t('[[node:title]] #[node:nid] [node:type-name] details updated!', array(), $options); } return array( '#tokens' => TRUE, '#markup' => $subject, ); case 'content': $content = array( '#type' => 'messaging_template_text', '#tokens' => TRUE, ); // Content type specific additional fields and modifications. if (isset($template_objects['node'])) { $content['teaser'] = _iq_notifications_generate_teaser($template_objects['node']); $content['author'] = t('Author: [node:author]', array(), $options); switch ($template_objects['node']->type) { case 'issue': // Handle if assignment not given yet, or reset to 'Not assigned'. if (!empty($template_objects['node']>field_issue_assigned_to[LANGUAGE_NONE])) {
93
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben $content['issue_assigned_to'] = t('Assigned to: [node:field-issue-assigned-to]', array(), $options); } $content += array( 'issue_status' => t('Status: [node:field-issuestatus]', array(), $options), 'issue_priority' => t('Priority: [node:field-issuepriority]', array(), $options), 'issue_type' => t('Type: [node:field-issue-type]', array(), $options), ); // Handle if Issue tags left empty. if (!empty($template_objects['node']->field_issue_tags)) { $content['issue_tags'] = t('Tags: [node:field-issuetags]', array(), $options); } break; case 'project': // Handle if Project tags left empty. if (!empty($template_objects['node']>field_project_tags)) { $content['project_tags'] = t('Tags: [node:fieldproject-tags]', array(), $options); }; break; case 'documentation': break; case 'news': break; case 'user_story': // "How to demo" and "Done if" fields are skipped. $content += array( 'user_story_project' => t('Project: [node:og-groupref]', array(), $options), 'user_story_status' => t('Status: [node:field-userstory-status]', array(), $options), 'user_story_business_value' => t('Business value: [node:field-user-story-business-value]', array(), $options), ); // Handle when estimate is left empty. if (!empty($template_objects['node']>field_user_story_estimate[LANGUAGE_NONE])) { $content['user_story_estimate'] = t('Estimate: [node:field-user-story-estimate] h', array(), $options); }; // Handle when Assigned to field is left empty. if (!empty($template_objects['node']>field_user_story_assigned_to[LANGUAGE_NONE])) { $content['user_story_assigned_to'] = t('Assigned to: [node:field-user-story-assigned-to]', array(), $options); } // Handle when User story tags field is left empty. if (!empty($template_objects['node']>field_user_story_tags)) { $content['user_story_tags'] = t('Tags: [node:fielduser-story-tags]', array(), $options); }; break; }
94
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben $content['language'] = t('Language: @language', array('@language' => _iq_notifications_langcode_to_language_callback($template_objects['nod e']->language)), $options); } return $content; case 'digest': return array( '#tokens' => TRUE, 'title' => '[node:title]', 'more' => t('Read more [node:url]', array(), $options), ); default: return parent::default_text($type, $options); } } /** * Footer text. */ function text_footer($options) { return array( '#tokens' => TRUE, 'more' => array( '#type' => 'messaging_link', '#text' => t('Read more', array(), $options), '#url' => '[node:url]', ), 'unsubscribe' => array( '#type' => 'messaging_link', '#text' => t('You can unsubscribe at', array(), $options), '#url' => '[user:unsubscribe-url]', ), ); } } /** * Template for node inserts. */ class Iq_Notifications_Node_Post_Template extends Iq_Notifications_Node_Event_Template { public function get_title() { return t('Template for node inserts'); } public function digest_fields() { return array('node:type', 'author:uid'); } /** * New nodes can be digested by type or by author. */ public function digest_line($field, $options = array()) { switch ($field) { case 'node:type': return t('[node:title] #[node:nid] by [author-name].', array(), $options); case 'author:uid': return t('New [node:type-name]: [node:title] #[node:nid]', array(), $options);
95
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben } } /** * Default texts for this template, may need token replacement. */ function default_text($type, $options) { switch ($type) { case 'subject': $subject = t('[[node:og-group-ref]] New [node:type-name]: [node:title] #[node:nid]', array(), $options); // Project CT specific subject. $template_objects = $this->get_objects(); if (isset($template_objects['node']) && $template_objects['node']->type == 'project') { $subject = t('New [node:type-name]: [node:title] #[node:nid]', array(), $options); } return array( '#tokens' => TRUE, '#markup' => $subject, ); default: return parent::default_text($type, $options); } } } /** * Template for node updates. */ class Iq_Notifications_Node_Update_Template extends Iq_Notifications_Node_Event_Template { public function get_title() { return t('Template for node updates'); } public function digest_fields() { return array('node:nid', 'author:uid'); } public function digest_line($field, $options = array()) { switch ($field) { case 'node:nid': return t('The [node:title] has been updated by [user:name] #[node:nid]', array(), $options); case 'author:uid': return t('Updated [node:type-name]: [node:title] #[node:nid]', array(), $options); } } } /** * Template for node comments. */ class Iq_Notifications_Node_Comment_Template extends Iq_Notifications_Node_Event_Template { public function get_title() { t('Template for node comments'); }
96
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
public function digest_fields() { return array('node:nid'); } public function digest_line($field, $options = array()) { switch ($field) { case 'node:nid': // TODO: Would make sense to include [node:title], if possible. return t('New comment by [comment:name]: [comment:title] #[node:nid]', array(), $options); case 'author:uid': // TODO: No such digest_field, is this needed at all? return t('Commented on [node:type-name]: [node:title] #[node:nid]', array(), $options); } } /** * Header text. */ function text_header($options) { return array( '#tokens' => TRUE, // TODO: Would make sense to include [node:title], if possible. '#markup' => t("Greetings [user:name], new comment by [comment:name]: [comment:title]", array(), $options), ); } /** * Default texts for this template, may need token replacement. */ function default_text($type, $options) { switch ($type) { case 'subject': return array( '#tokens' => TRUE, '#markup' => t('[[node:og-group-ref]] New comment for [node:type-name]: [node:title] #[node:nid]', array(), $options), ); case 'content': $template_objects = $this->get_objects(); $content = array( '#tokens' => TRUE, 'comment_content' => '[comment:body]', ); // If it's an Issue ct, we add some extra fields to mail template. if (isset($template_objects['node']) && $template_objects['node']->type == 'issue') { $issue_details = array(); if (!empty($template_objects['node']>field_issue_assigned_to[LANGUAGE_NONE])) { $issue_details['issue_assigned_to'] = t('Assigned to: [node:field-issue-assigned-to]', array(), $options); } $issue_details += array(
97
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben 'issue_status' => t('Status: [node:field-issue-status]', array(), $options), 'issue_priority' => t('Priority: [node:field-issuepriority]', array(), $options), 'issue_type' => t('Type: [node:field-issue-type]', array(), $options), ); if (!empty($template_objects['node']->field_issue_tags)) { $issue_details['issue_tags'] = t('Labels: [node:fieldissue-tags]', array(), $options); } $content = array_merge($content, $issue_details); } $content['language'] = t('Language: @language', array('@language' => _iq_notifications_langcode_to_language_callback($template_objects['nod e']->language)), $options); return $content; case 'digest': return array( '#tokens' => TRUE, 'title' => '[node:title]', 'more' => t('Read more [node:url]#comment-[comment:cid]', array(), $options), ); default: return parent::default_text($type, $options); } } /** * Footer text. */ function text_footer($options) { $footer = parent::text_footer($options); // Modify comment's more link. $footer['more'] = array( '#type' => 'messaging_link', '#text' => t('Read more', array(), $options), '#url' => '[node:url]#comment-[comment:cid]', ); return $footer; } }
98
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.7.
_IQ_NOTIFICATIONS_GENERATE_TEASER()
/** * Generate content specific teaser for notifications. * * @param $node * Node object. */ function _iq_notifications_generate_teaser($node) { $body_field = ''; switch ($node->type) { case 'documentation': $body_field = 'field_documentation_body'; break; case 'issue': $body_field = 'field_issue_body'; break; case 'news': $body_field = 'field_news_body'; break; case 'project': $body_field = 'field_project_body'; break; case 'user_story': $body_field = 'field_user_story_description'; break; default: $body_field = 'field_body'; break; } $node_body = $node->$body_field; $teaser = ''; if (isset($node_body[LANGUAGE_NONE][0]['value'])) { $format = $node_body[LANGUAGE_NONE][0]['format'] ? $node_body[LANGUAGE_NONE][0]['format'] : 'filtered_html'; $teaser = text_summary($node_body[LANGUAGE_NONE][0]['value'], $format, 300); } return check_plain($teaser); }
99
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.8.
SEARCH API MULTI INDEX FACETS RÖVID TÖRTÉNETE
A Drupal sandbox (kisérleti) moduljai közt publikált Search API Multi Index Facets modul fejlesztője azt írta a modul leírásban, hogy ezzel a modullal működésre tudta bírni a faceteket a több indexet használó keresőoldal esetében is, azonban csak az Apache Solr szervert használó Search API Solr modullal tesztelte. Okot nem láttam, hogy egy próbát miért ne érne meg a modul kipróbálása MySQL szervert használva is, azonban azt kellett látnom, hogy nem működik a modul. Hiába nyújtott egy megfelelő adminisztrációs felületet a Search API szerver beállításánál a szerver található indexekben elérhető facetek kiválasztására, de utána képtelen azokat használni, megjeleníteni. Elsőre talán még el is hittem, hogy ez a MySQL szerver miatt van, bár a kódot átvizsgálva nem találtam rá okot. Mivel más megoldási lehetőséget akkor még nem láttam ezért hosszas próbálkozás után sikerült telepítenem az Ubuntu szerveremen az Apache Solr szervert és letesztelni a modult a Solr szerveren is. Ott sem működött. Mivel ez egy kisérleti projekt csak, így várható volt, hogy vannak benne hibák (pl: csak a modulfüggőségeinek megfelelő verzióival működik jól), ezért elkezdtem ezeket párosítgatni majd debuggolni a kódot, hogy megtaláljam, hol lehet a probléma. Jó pár problémát sikerült megtalálnom, de a multi facetek még mindig nem akartak működni. Végül felvettem a modul fejlesztőjével a kapcsolatot, hogy segítséget kérjek tőle a modul működésére bírásával. A beszélgetésünk lényege összefoglalható úgy, hogy ez a modul valamikor működőképes volt egy olyan patchelt (módosított) Search API modullal, amelyet ő készített és az óta nem is fejlesztett tovább. A patchelt Search API modul már nincs meg neki, vagy nem találja, segítséget nem tud nyújtani a modul kijavításához. Tehát elmondható, hogy a történet valahol hasonlít az OG Notifications problémájára, de itt esélyem se volt a modul kijavítására, hiszen egy olyan kódbázist kellett volna kijavítanom, amely amúgy is csak bizonyos módosított körülmények közt volt valaha is működőképes.
100
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.9.
OPENSPACE_SEARCH_HACK.MODULE ELSŐ VERZIÓ RÉSZLET
/** * Convert index information from handler to search index table name and the field name in table. * * @param $index_info * Information from handler which contains the index and the field name in index. * @return array * Which contains the search index table name and the field name in table. */ function _openspace_search_hack_get_index_tables($index_info) { $tables_datas = array(); foreach ($index_info as $field) { $data = explode(':', $field); $tables_datas[] = array( 'name' => $data[0], 'field' => strtolower($data[1]), ); } return $tables_datas; } /** * Process the views result into an array which contains the entity ids of results, separated by indexes. * * @param $view_result * Views result object which contains the entity information. * @return array * An array which contains the entity ids, separated by index information. * Ex.: $entity_info['commentindex'][0] = '12345'; where 12345 the entity id of comment. * This will be useful when we need to join node table for example to comment index, but only to comment index. */ function _openspace_search_hack_get_result_information($view_result) { $entity_infos = array(); if (!empty($view_result)) { foreach ($view_result as $result) { $entity_infos[$result>_entity_properties['search_api_multi_index']][] = $result->entity; } } return $entity_infos; } /** * Get available author list for author select. * TODO use it in other selects if it is possible. * TODO anyways, handle uid0, they should not be on the list... * * @param $index_data
101
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben * Array of search api multi indexes which contains the index- and field names where search. * @param $view_result * View result object which contains the current result of view. * This help us the reduce the available options for other filters (selects). * @return array * Return an array which can be used as an option in selects. */ function _openspace_search_hack_get_available_authors($index_data, $view_result) { // Get the index tables and the field names in these tables. $table_datas = _openspace_search_hack_get_index_tables($index_data); // Get all entities' information from the current view result. $entity_infos = _openspace_search_hack_get_result_information($view_result); // TODO if we used this function in other purposes, these should be moved parameters too. // Some basic data. $sapi_db_prefix = 'search_api_db_'; $main_table = 'users'; $main_fields = array('uid', 'name'); $main_id = 'uid'; if (!empty($entity_infos)) { $aux_queries = array(); // Create queries for all given indexes. foreach ($table_datas as $table) { // Only create query if there is a construction in $entity_infos this table. if (isset($entity_infos[$table['name']])) { $table_name = $sapi_db_prefix . $table['name']; $aux_query = db_select($table_name) ->fields($table_name, array($table['field'])); $aux_query->condition('item_id', $entity_infos[$table['name']], 'IN'); $aux_queries[] = $aux_query; } } // It is sure that we have one queries at least. But if we have more than one we need to union that into one query. $aux_queries_all = $aux_queries[0]; if (count($aux_queries) != 1) { for ($i = 1; $i < count($aux_queries); $i++) { $aux_queries_all = $aux_queries_all->union($aux_queries[$i]); } } // Get the result of these queries to used that as a condition in our main query. $entity_ids = $aux_queries_all->execute()->fetchCol(); } // Finally, get the result of our main query. Use anxious queries result as a filter. $main_query = db_select($main_table) ->fields($main_table, $main_fields);
102
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben if (!empty($entity_infos)) { $main_query->condition($main_id, $entity_ids, 'IN'); } $main_result = $main_query->execute()->fetchAllAssoc($main_id); // Transform the result to a select form item's #options array. $options = array(); foreach ($main_result as $result) { $options[$result->uid] = $result->name; } return $options; } function _openspace_search_hack_get_available_node_types($index_data, $view_result) { $table_datas = array(); foreach ($index_data as $field) { $data = explode(':', $field); $data[1] = str_replace(' » ', '_', $data[1]); $data[1] = str_replace('Content ', '', $data[1]); $table_datas[] = array( 'name' => $data[0], 'field' => strtolower($data[1]), ); } // Get the index tables and the field names in these tables. // $table_datas = _openspace_search_hack_get_index_tables($index_data); // Get all entities' information from the current view result. $entity_infos = _openspace_search_hack_get_result_information($view_result); // Get the field name, which will be used in the result of union query. (If we'll use union...) $indexes = array_keys($table_datas); $index_id = $table_datas[$indexes[0]]['field']; // TODO if we used this function in other purposes, these should be moved parameters too. // Some basic data. $sapi_db_prefix = 'search_api_db_'; $main_table = 'node_type'; $main_fields = array('type', 'name'); $main_id = 'type'; $aux_queries = array(); // Create queries for all given indexes. foreach ($table_datas as $table) { // Only create query if there is a construction in $entity_infos this table. if (isset($entity_infos[$table['name']])) { $table_name = $sapi_db_prefix . $table['name']; $aux_query = db_select($table_name) ->fields($table_name, array($table['field'])); $aux_query->condition('item_id', $entity_infos[$table['name']], 'IN'); $aux_queries[] = $aux_query; } }
103
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
// It is sure that we have one queries at least. But if we have more than one we need to union that into one query. $aux_queries_all = $aux_queries[0]; if (count($aux_queries) != 1) { for ($i = 1; $i < count($aux_queries); $i++) { $aux_queries_all = $aux_queries_all->union($aux_queries[$i]); } } // Get the result of these queries to used that as a condition in our main query. $entity_ids = $aux_queries_all->execute()->fetchCol(); // Finally, get the result of our main query. Use anxious queries result as a filter. $main_query = db_select($main_table) ->fields($main_table, $main_fields) ->condition($main_id, $entity_ids, 'IN'); $main_result = $main_query->execute()->fetchAllAssoc($main_id); // Transform the result to a select form item's #options array. $options = array(); foreach ($main_result as $result) { $options[$result->type] = $result->name; } return $options; }
104
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.10.
HANDLER_FILTER_FULLTEXT.INC
value)) { $this->value = $this->value ? reset($this->value) : ''; } // Catch empty strings entered by the user, but not "0". if ($this->value === '') { return; } $fields = $this->options['fields']; // If something already specifically set different fields, we silently fall // back to mere filtering. $filter = $this->options['mode'] == 'filter'; if (!$filter) { $old = $this->query->getFields(); $filter = $old && $fields && (array_diff($old, $fields) || array_diff($fields, $old)); } if ($filter) { $filter = $this->query->createFilter('OR'); foreach ($fields as $field) { $filter->condition($field, $this->value, $this->operator); } $this->query->filter($filter); return; } // is set // if
If the operator was set to OR, set it as the conjunction. (AND by default.) ($this->operator === 'OR') { $this->query->setOption('conjunction', $this->operator);
}
105
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
if ($fields) { $this->query->fields($fields); } $old = $this->query->getOriginalKeys(); $this->query->keys($this->value); if ($this->operator == 'NOT') { $keys = &$this->query->getKeys(); if (is_array($keys)) { $keys['#negation'] = TRUE; } else { // We can't know how negation is expressed in the server's syntax. } } if ($old) { $keys = &$this->query->getKeys(); if (is_array($keys)) { $keys[] = $old; } elseif (is_array($old)) { // We don't support such nonsense. } else { $keys = "($old) ($keys)"; } } } /** * Helper method to get an option list of all available fulltext fields. */ protected function getFulltextFields() { $fields = array(); $indexes = search_api_index_load_multiple(FALSE, array('enabled' => TRUE)); foreach ($indexes as $index) { if ($index->getFields()) { $prefix = $index->machine_name . ':'; $prefix_name = $index->name . ' » '; $f = $index->getFields(); foreach ($index->getFulltextFields() as $name) { $fields[$prefix . $name] = $prefix_name . $f[$name]['name']; } } } return $fields; } }
106
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.11. _IQ_SEARCH_PREPROCESS_SEARCH_API_RESULT() /** * Prepossess Search API Facet API (S.A.F.A.) result array. * This array contains both comment index and content index fields results, * but separated. We need to sum up the coherent arrays. * * @param $result * S.A.F.A result array. * @return array * A modified result array. 'node:' prefixes removed and coherent arrays merged. * After that the coherent indexes merged too to reduce duplicate indexes. * (Counts were also summarized in this indexes.) */ function _iq_search_preprocess_search_api_result($result) { $keys = array_keys($result); // Cleaning up 'node:' prefixes and ':uid' suffix from keys. // This helps to find out which arrays are coherent and merge them. // example: node_og_group_ref and og_group_ref are coherent arrays, their data should be merged in next step! foreach ($keys as $key) { $cleankey = str_replace(array('node:', ':uid'), '', $key); if ($cleankey == $key) { continue; } else { if (isset($result[$cleankey])) { $result[$cleankey] = array_merge($result[$cleankey], $result[$key]); unset($result[$key]); } else { $result[$cleankey] = $result[$key]; unset($result[$key]); } } } // Merge coherent array indexes into one array to reduce duplicates. // Facet API count information will be summarized too. $concated_result = array(); $keys = array_keys($result); foreach ($keys as $key) { foreach ($result[$key] as $data) { $concated_result[$key][$data['filter']] = array( 'filter' => str_replace('"', '', $data['filter']), 'count' => isset($concated_result[$key][$data['filter']]['count']) ? $concated_result[$key][$data['filter']]['count'] + $data['count'] : $data['count'], ); } } return $concated_result; }
107
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.12.
IQ_SEARCH_VIEWS_POST_RENDER()
/** * Implements hook_views_post_render(). */ function iq_search_views_post_render($view, &$output) { // Add our custom form to search api multi search view page. if ($view->name == 'search_multi' && $view->current_display == 'page') { $fake_form = drupal_get_form('fake_views_exposed_search_multi_form', $view); $output = drupal_render($fake_form) . $output; } }
108
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.13.
FAKE_VIEWS_EXPOSED_SEARCH_MULTI_FORM()
/** * This custom form will be used in Search API Multi search view page * instead of the basic dummy exposed form. * This will create a select list for each filters and fill them with * options from Search API Facet API results. * * @param $view * A views view object. */ function fake_views_exposed_search_multi_form($form, $form_state, $view) { // Get all form elements from original exposed forms. $form_state = array( 'view' => $view, 'display' => $view->display['default'], 'exposed_form_plugin' => $view->display['default']>display_options['exposed_form'], ); $original_form = drupal_build_form('views_exposed_form', $form_state); // Get Facet API result for current search. $sapi_result = method_exists($view->query, 'getSearchApiResults') ? $view->query->getSearchApiResults() : array(); // Check if Facet API has result this search. if (isset($sapi_result['search_api_facets'])) { $facetapi_multi_result = _iq_search_preprocess_search_api_result($sapi_result['search_api_facet s']); // Create our custom Author filter. if (isset($original_form['search_api_multi_author']) && isset($facetapi_multi_result['author'])) { $search_table = array( 'table' => 'users', 'fields' => array('name'), 'unique_id' => 'uid', ); $author_options = _iq_search_generate_options($facetapi_multi_result['author'], $search_table); $form['search_api_multi_author'] = array( '#type' => 'select', '#title' => t('Author'), '#options' => array('All' => t('- Any -')) + $author_options, '#default_value' => isset($form_state['input']['search_api_multi_author']) ? $form_state['input']['search_api_multi_author'] : $original_form['search_api_multi_author']['#value'], ); } // Create our custom Project filter.
109
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben if (isset($original_form['search_api_multi_project']) && isset($facetapi_multi_result['og_group_ref'])) { $search_table = array( 'table' => 'node', 'fields' => array('title'), 'unique_id' => 'nid', ); $project_options = _iq_search_generate_options($facetapi_multi_result['og_group_ref'], $search_table); $form['search_api_multi_project'] = array( '#type' => 'select', '#title' => t('Project'), '#options' => array('All' => t('- Any -')) + $project_options, '#default_value' => isset($form_state['input']['search_api_multi_project']) ? $form_state['input']['search_api_multi_project'] : $original_form['search_api_multi_project']['#value'], ); } // Create our custom Node type filter. if (isset($original_form['search_api_multi_node_type']) && isset($facetapi_multi_result['type'])) { $search_table = array( 'table' => 'node_type', 'fields' => array('name'), 'unique_id' => 'type', ); $node_type_options = _iq_search_generate_options($facetapi_multi_result['type'], $search_table); $form['search_api_multi_node_type'] = array( '#type' => 'select', '#title' => t('Node type'), '#options' => array('All' => t('- Any -')) + $node_type_options, '#default_value' => isset($form_state['input']['search_api_multi_node_type']) ? $form_state['input']['search_api_multi_node_type'] : $original_form['search_api_multi_node_type']['#value'], ); } $form['#attached']['js'] = array( drupal_get_path('module', 'iq_search') . '/js/fake_exposed_filters.js', ); $form['#attached']['css'] = array( drupal_get_path('module', 'iq_search') . '/css/fake_exposed_filters.css', ); } return $form; }
110
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.14. _IQ_SEARCH_GENERATE_OPTIONS() /** * Generate an options array for selects from one preprocessed S.A.F.A result array. * * @param array $fields * The preprocessed (merged and summarized) S.A.F.A result array. * @param array $search_table * The base table in database, where we get the human readable name/title of one option. * @return array * Return a proper options array for selects. */ function _iq_search_generate_options($fields = array(), $search_table = array()) { $options = array(); foreach ($fields as $field) { $query = db_select($search_table['table']) ->fields($search_table['table'], $search_table['fields']) ->condition($search_table['unique_id'], $field['filter']); if (isset($search_table['conditions'])) { foreach ($search_table['conditions'] as $condition) { $query->condition($condition['field'], $condition['value'], '='); } } $result = $query->execute()->fetchField(); $options[$field['filter']] = $result . ' (' . $field['count'] . ')'; } return $options; }
111
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.15.
FAKE_EXPOSED_FILTER.JS
/** * @file * Pass data from fake exposed form to the real one. */ (function ($) { Drupal.behaviors.fakeExposedFilters = { attach: function (context, settings) { $(document).ready(function () { // Set some important variable. var fake_form_id = 'form#fake-views-exposed-searchmulti-form '; var real_form_id = 'form#views-exposed-form-searchmulti-page '; // Author field. var fake_author_field = fake_form_id + 'select[name="search_api_multi_author"]'; var real_author_field = real_form_id + 'input[name="search_api_multi_author"]'; if ($(real_author_field).length) { $(real_author_field).val($(fake_author_field).val()); $(fake_author_field).change(function () { $(real_author_field).val($(this).val()); $(real_form_id).submit(); }); } // Project field. var fake_project_field = fake_form_id + 'select[name="search_api_multi_project"]'; var real_project_field = real_form_id + 'input[name="search_api_multi_project"]'; if ($(real_project_field).length) { $(real_project_field).val($(fake_project_field).val()); $(fake_project_field).change(function () { $(real_project_field).val($(this).val()); $(real_form_id).submit(); }); } // Node type field. var fake_node_type_field = fake_form_id + 'select[name="search_api_multi_node_type"]'; var real_node_type_field = real_form_id + 'input[name="search_api_multi_node_type"]'; if ($(real_node_type_field).length) { $(real_node_type_field).val($(fake_node_type_field).val()); $(fake_node_type_field).change(function () { $(real_node_type_field).val($(this).val()); $(real_form_id).submit(); }); } }); } } })(jQuery);
112
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.16.
IQ_SEARCH_FORM_VIEWS_EXPOSED_FORM_ALTER()
/** * Hide real filters on exposed forms. * * Implements hook_form_FORM_ID_alter(). */ function iq_search_form_views_exposed_form_alter(&$form, $form_state) { if ($form['#id'] == 'views-exposed-form-search-multi-page') { if (isset($form['search_api_multi_author'])) { $form['search_api_multi_author']['#type'] = 'hidden'; $form['#info']['filter-search_api_multi_author']['label'] = ''; } if (isset($form['search_api_multi_project'])) { $form['search_api_multi_project']['#type'] = 'hidden'; $form['#info']['filter-search_api_multi_project']['label'] = ''; } if (isset($form['search_api_multi_node_type'])) { $form['search_api_multi_node_type']['#type'] = 'hidden'; $form['#info']['filter-search_api_multi_node_type']['label'] = ''; } } }
113
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben
1.17. COMMENT ACCESS CHECK PATCH A SEARCH API-HOZ diff --git a/includes/callback_comment_access.inc b/includes/callback_comment_access.inc new file mode 100644 index 0000000..e627353 --- /dev/null +++ b/includes/callback_comment_access.inc @@ -0,0 +1,46 @@ +getEntityType() === 'comment'; + } + + /** + * Overrides SearchApiAlterNodeAccess::getNode(). + * + * Returns the comment's node, instead of the item (i.e., the comment) itself. + */ + protected function getNode($item) { + return node_load($item->nid); + } + + /** + * Overrides SearchApiAlterNodeAccess::configurationFormSubmit(). + * + * Doesn't index the comment's "Author". + */ + public function configurationFormSubmit(array $form, array &$values, array &$form_state) { + $old_status = !empty($form_state['index']>options['data_alter_callbacks']['search_api_alter_comment_access']['s tatus']); + $new_status = !empty($form_state['values']['callbacks']['search_api_alter_comment_ac cess']['status']); + + if (!$old_status && $new_status) { + $form_state['index']->options['fields']['status']['type'] = 'boolean'; + } + + return parent::configurationFormSubmit($form, $values, $form_state);
114
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben + } + +} diff --git a/includes/callback_node_access.inc b/includes/callback_node_access.inc index 5acc76c..8bfab49 100644 --- a/includes/callback_node_access.inc +++ b/includes/callback_node_access.inc @@ -10,15 +10,9 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback { /** * Check whether this data-alter callback is applicable for a certain index. + * Overrides SearchApiAbstractAlterCallback::supportsIndex(). * * Returns TRUE only for indexes on nodes. * * @param SearchApiIndex $index * The index to check for. * * @return boolean * TRUE if the callback can run on the given index; FALSE otherwise. */ public function supportsIndex(SearchApiIndex $index) { // Currently only node access is supported. @@ -26,15 +20,9 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback { } /** * Declare the properties that are (or can be) added to items with this callback. + * Overrides SearchApiAbstractAlterCallback::propertyInfo(). * * Adds the "search_api_access_node" property. * * @see hook_entity_property_info() * * @return array * Information about all additional properties, as specified by * hook_entity_property_info() (only the inner "properties" array). */ public function propertyInfo() { return array( @@ -47,15 +35,7 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback { } /** * Alter items before indexing. * * Items which are removed from the array won't be indexed, but will be marked * as clean for future indexing. This could for instance be used to implement * some sort of access filter for security purposes (e.g., don't index
115
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben +
* unpublished nodes or comments). * * @param array $items * An array of items to be altered, keyed by item IDs. * {@inheritdoc} */ public function alterItems(array &$items) { static $account; @@ -65,30 +45,39 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback { $account = drupal_anonymous_user(); } + +
foreach ($items as $nid => &$item) { foreach ($items as $id => $item) { $node = $this->getNode($item); // Check whether all users have access to the node. if (!node_access('view', $item, $account)) { + if (!node_access('view', $node, $account)) { // Get node access grants. $result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = :nid) AND grant_view = 1', array(':nid' => $item->nid)); + $result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = :nid) AND grant_view = 1', array(':nid' => $node->nid)); +
// Store all grants together with it's realms in the item. // Store all grants together with their realms in the item. foreach ($result as $grant) { if (!isset($items[$nid]->search_api_access_node)) { $items[$nid]->search_api_access_node = array(); } $items[$nid]->search_api_access_node[] = "node_access_$grant->realm:$grant->gid"; + $items[$id]->search_api_access_node[] = "node_access_{$grant->realm}:{$grant->gid}"; } } else { // Add the generic view grant if we are not using node access or the // node is viewable by anonymous users. $items[$nid]->search_api_access_node = array('node_access__all'); + $items[$id]->search_api_access_node = array('node_access__all'); } } } /** * Submit callback for the configuration form. + * Retrieves the node related to a search item. + * + * In the default implementation for nodes, the item is already the node. + * Subclasses may override this to easily provide node access checks for + * items related to nodes. + */ + protected function getNode($item) { + return $item;
116
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben + } + + /** + * Overrides SearchApiAbstractAlterCallback::configurationFormSubmit(). * * If the data alteration is being enabled, set "Published" and "Author" to * "indexed", because both are needed for the node access filter. diff --git a/search_api.info b/search_api.info index c03de9d..5b65f83 100644 --- a/search_api.info +++ b/search_api.info @@ -11,6 +11,7 @@ files[] = includes/callback_add_hierarchy.inc files[] = includes/callback_add_url.inc files[] = includes/callback_add_viewed_entity.inc files[] = includes/callback_bundle_filter.inc +files[] = includes/callback_comment_access.inc files[] = includes/callback_language_control.inc files[] = includes/callback_node_access.inc files[] = includes/callback_node_status.inc diff --git a/search_api.module b/search_api.module index 395a230..d2ad28c 100644 --- a/search_api.module +++ b/search_api.module @@ -1012,6 +1012,11 @@ function search_api_search_api_alter_callback_info() { 'description' => t('Add node access information to the index.'), 'class' => 'SearchApiAlterNodeAccess', ); + $callbacks['search_api_alter_comment_access'] = array( + 'name' => t('Access check'), + 'description' => t('Add node access information to the index.'), + 'class' => 'SearchApiAlterCommentAccess', + ); $callbacks['search_api_alter_node_status'] = array( 'name' => t('Exclude unpublished nodes'), 'description' => t('Exclude unpublished nodes from the index.'), @@ -1779,39 +1784,68 @@ function search_api_get_processors() { * The SearchApiQueryInterface object representing the search query. */ function search_api_search_api_query_alter(SearchApiQueryInterface $query) { + global $user; $index = $query->getIndex(); // Only add node access if the necessary fields are indexed in the index, and // unless disabled explicitly by the query. - $fields = $index->options['fields']; - if (!empty($fields['search_api_access_node']) && !empty($fields['status']) && !empty($fields['author']) && !$query>getOption('search_api_bypass_access')) { $account = $query->getOption('search_api_access_account', $GLOBALS['user']); + $type = $index->getEntityType(); + if (!empty($index>options['data_alter_callbacks']["search_api_alter_{$type}_access"]) && !$query->getOption('search_api_bypass_access')) { + $account = $query->getOption('search_api_access_account', $user); if (is_numeric($account)) {
117
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben $account = user_load($account); } if (is_object($account)) { try { _search_api_query_add_node_access($account, $query); + _search_api_query_add_node_access($account, $query, $type); } catch (SearchApiException $e) { watchdog_exception('search_api', $e); } } else { watchdog('search_api', 'An illegal user UID was given for node access: @uid.', array('@uid' => $query>getOption('search_api_access_account', $GLOBALS['user'])), WATCHDOG_WARNING); + watchdog('search_api', 'An illegal user UID was given for node access: @uid.', array('@uid' => $query>getOption('search_api_access_account', $user)), WATCHDOG_WARNING); } } } /** - * Build a node access subquery. - * - * @param $account - * The user object, who searches. - * - * @return SearchApiQueryFilter - */ -function _search_api_query_add_node_access($account, SearchApiQueryInterface $query) { - if (!user_access('access content', $account)) { + * Adds a node access filter to a search query, if applicable. + * + * @param object $account + * The user object, who searches. + * @param SearchApiQueryInterface $query + * The query to which a node access filter should be added, if applicable. + * @param string $type + * (optional) The type of search – either "node" or "comment". Defaults to + * "node". + * + * @throws SearchApiException + * If not all necessary fields are indexed on the index. + */ +function _search_api_query_add_node_access($account, SearchApiQueryInterface $query, $type = 'node') { + // Don't do anything if the user can access all content. + if (user_access('bypass node access', $account)) { + return; + } + + $is_comment = ($type == 'comment'); + + // Check whether the necessary fields are indexed. + $fields = $query->getIndex()->options['fields']; + $required = array('search_api_access_node', 'status');
118
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben + if (!$is_comment) { + $required[] = 'author'; + } + foreach ($required as $field) { + if (empty($fields[$field])) { + $vars['@field'] = $field; + $vars['@index'] = $query->getIndex()->name; + throw new SearchApiException(t('Required field @field not indexed on index @index. Could not perform access checks.', $vars)); + } + } + + // If the user cannot access content/comments at all, return no results. + if (!user_access('access content', $account) || ($is_comment && !user_access('access content', $account))) { // Simple hack for returning no results. $query->condition('status', 0); $query->condition('status', 1); @@ -1819,29 +1853,28 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu return; } + + +
// Only filter for user which don't have full node access. if (!user_access('bypass node access', $account)) { // Filter by node "published" status. if (user_access('view own unpublished content')) { $filter = $query->createFilter('OR'); $filter->condition('status', NODE_PUBLISHED); $filter->condition('author', $account->uid); $query->filter($filter); } else { $query->condition('status', NODE_PUBLISHED); } // Filter by node access grants. // Filter by the "published" status. $published = $is_comment ? COMMENT_PUBLISHED : NODE_PUBLISHED; if (!$is_comment && user_access('view own unpublished content')) { $filter = $query->createFilter('OR'); $grants = node_access_grants('view', $account); foreach ($grants as $realm => $gids) { foreach ($gids as $gid) { $filter->condition('search_api_access_node', "node_access_$realm:$gid"); } } $filter->condition('search_api_access_node', 'node_access__all'); + $filter->condition('status', $published); + $filter->condition('author', $account->uid); $query->filter($filter); } + else { + $query->condition('status', $published); + } + + // Filter by node access grants. + $filter = $query->createFilter('OR'); + $grants = node_access_grants('view', $account);
119
Az Openspace ügykezelőrendszer megvalósítása Drupal tartalomkezelő rendszerben + foreach ($grants as $realm => $gids) { + foreach ($gids as $gid) { + $filter->condition('search_api_access_node', "node_access_$realm:$gid"); + } + } + $filter->condition('search_api_access_node', 'node_access__all'); + $query->filter($filter); } /**
120