Archive for the ‘Drupal’ Category

Rendszeresen megkeresnek azzal a kéréssel, hogy hogyan kell egyedi Drupal sminket készíteni, mert nem akrarnyak egy már elkészített contrib fejlesztést használni.
Én is általában azt szoktam javasolni, hogy inkább készítsük el saját magunk a meglévő psd alapján a sminkünket, mert igaz az esetek legtöbbségében meg lehetne oldani valamely már meglévő fejlesztés css-ének módosításával, de az mégsem az igazi. Egyrészt azért, mert a contrib sminkek több dologra vannak felkészítve mint amire nekünk szükségünk van, másrészt a végeredményben úgyis egy 3000 soros css-t kapunk amiben egy rakás osztály, vagy id több helyen van definiálva. Másrészt ahogy a contrib modulokba se szokás beleirkálni, szerintem a sminkeket se kell piszkálni.

Ha saját sminket készítünk, saját tpl és css fájlokkal akkor ezek a problémák kiküszöbölhetőek, és nem egy ördöngös feladat.

Leszögezném, hogy ez a bejegyzés csak az alapokról szól, nagyon sok egyéb dologra felkészíthetjük a sminkünket, pl: $logo, $site_slogan, $mission, $search_box stb. Az elérhető változókat itt lehet megnézni.

Az első lépés az, hogy a psd alapján végig kell gondolnunk, hogy milyen régiókra lesz szükségünk. Persze könnyen lehet, hogy az alapértelmezett régiók bőven elegendőek, de általában nekem újakat kell létrehoznom. Itt egy fontos megjegyzés az, hogy az alapértelmezett régiók elvesznek ha akár csak egy sajátot is létrehozunk, ha az info fájlba definiálunk régiót, akkor az összeset meg kell adnunk!

Miután ez megvan elkezdhetjük a sminkünk elkészítését, vagyis a fizikai fájlok létrehozását.

A saját sminkeket különítsük el a core sminkektől, ezért hozzuk létre a sites/all/themes könyvtárat és ide helyezzük a contrib, illetve a custom sminkeket.

Hozzuk létre a sminkemneve könyvtárat és abba készítsük a sminkemneve.info fájlt. Fontos megjegyzés, hogy az info fájlokat a rendszer cache-eli, így ha abba módosítunk valamit, akkor be kell menni az admin/build/themes könyvtárba és nyomni egy mentést, ekkor újra menti az info fájl tartalmát.

De hogy is néz ki egy info fájl? Erről egy teljesen részletes leírás található itt. Az alapértelmezett értékek pedig itt szerepelnek (itt láthatjuk pl a default régiókat).

Íme egy info fájl:

  1. name = My theme's name
  2. description = My theme's description.
  3. version = VERSION
  4. core = 6.x
  5. engine = phptemplate
  6. stylesheets[all][] = style.css
  7. stylesheets[all][] = layout.css
  8. scripts[] = scripts.js
  9. regions[header] = Header
  10. regions[left_slidebar] = Left sidebar
  11. regions[left_banner] = Left banner
  12. regions[content_top] = Content top
  13. regions[in_top_of_content] = In top of content
  14. regions[content] = Content
  15. regions[right_slidebar] = Right sidebar
  16. regions[right_banner] = Right Banner
  17. regions[footer_top] = Footer top
  18. regions[footer_bottom] = Footer bottom

Talán csak annyit fűznék még hozzá, hogy a regions tömb kulcsa lesz a page.tpl.php-ban elérhető változó ami az adott régió tartalmát adja vissza html string formában.

Ezután jöhet a page.tpl.php.
Ebben állítjuk össze magát a layout-ot, tehát létrehozzuk a szükséges div-eket a psd implementálásához.

Én így szoktam kezdeni:

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2.   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
  4.   <head>
  5.     <?php print $head ?>
  6.     <title><?php print $head_title ?></title>
  7.     <?php print $styles ?>
  8.     <?php print $scripts ?>
  9.   </head>
  10.   <body class="<?php print $body_classes ?>">

Szerintem elég egyértelmű hogy mit is csinál, csak annyit fűznék hozzá, hogy a $body_classes változó egy csomó információt tartalmaz, pl hogy nyitólapon vagyunk-e vagy, be vagyunk-e jelentkezve vagy hogy mely régiókra lesz szükség. Ez szerintem hasznos lehet ha azt akarjuk hogy a sminkünk máshogy nézzen ki pl ha csak a jobb oldali slidebar létezik, vagy ha mind a kettő, esetleg egyik sem stb.

Ezután kezdjük el kialakítani a szükséges div-eket, ez nyilván a psd-től függ.

Jöhetnek a régiók! Ha nem akarjuk teleszemetelni üres div-ekkel a template-et akkor feltételek közé rakjuk hogy egy adott régiót körülvevő div megjelenjen, pl így:

  1. <?php if ($left_slidebar) : ?>
  2.   <div id="left_slidebar"><?php print $left_slidebar ?></div>
  3. <?php endif; ?>

Miután a div-ekkel megvagyunk, készítsük el a tabokat (view, edit stb), a rendszerüzeneteket (pl hibás felhasználónév) írassuk ki magát a content-et - esetleg az oldal címét is beletehetjük a page.tpl.php-ba:

  1. <?php print $title ?>
  2. <?php if ($tabs): print '<div id="tabs-wrapper" class="clear-block">'; endif; ?>
  3. <?php if ($tabs): print '<ul class="tabs primary">'. $tabs .'</ul></div>'; endif; ?>
  4. <?php if ($messages): print $messages; endif; ?>
  5. <?php print $content ?>

Végül egy fontos feladatunk van a page.tpl.php-vel, az pedig a $closure kiíratása:

  1. <?php echo $closure; ?>

Ez azért fontos, mert sok modul ebbe pakol dolgokat, pl a WYSIWYG modul esetében azt érzékeljük, hogy egyszerűen nem jelenik meg az editor ha ez kimaradt a sminkfájlból.

Amint írtam van még egy csomó dolog amiket beletehetünk a sminkünkbe (és ha a közössésgnek készítjük a sminket akkor bele is kell rakni), ezen változók listáját itt lehet megnézni.

A page.tpl.php-n kívül még létre kell hozunk a block.tpl.php-t, a comment.tpl.php-t és a node.tpl.php-t. Ha az alap sminkfájlokat használjuk, akkor tökéletesen működik a sminkünk.
Persze ezeket is egyedileg testre kell szabi, ezeket is felosztjuk a megfelelő div-ekkel hogy olyan kinézetet kapjunk mint a psd-ben.

Van még egy fájl ami általában kell, ez pedig a template.php. Fontos, hogy az ebben definiált fügvények nevét cache-eli a rendszer, tehát ha egy új fügvényt deklarálunk akkor be kell menni az admin/build/themes oldalra és menteni kell.

A WYSIWYG editorok lényege, hogy a felhasználó HTML tudás nélkül is tudjon webes felületen formázni. Ezt úgy érjük el, hogy a html szöveget tartalmazó textarea-ra egy JavaScript szerkesztőfelületet adunk, ami a begépelt és formázott szöveget HTML kódokká alakítja át.

Drupal alá is létezik sok közkedvelt WYSIWYG editor modul, pl: FCK Editor vagy a TinyMCE Editor, melyek mindig előkelő helyen vannak a leggyakrabban letöltött Drupal modulok között.

Nekem a kedvencem mégis a Wysiwyg API modul, mégpedig azért, mert egy alapvető problémát old meg: a beviteli formák összeütközése a WYSIWYG editorral a beviteli forma szűrői miatt. Ez röviden annyit takar, hogy a többi WYSIWYG editor jogosultsághoz van rendelve, tehát ha egy felhasználónak megvan a joga az editorhoz akkor használhatja, de ha kiválasztja Filetred HTML beviteli formát, amelyben a sortörések, url-ek stb. szűrve vannak, akkor az eléggé össze fog akadni a WYSIWYG editorral. Ilyenkor ugyebár a felhasználó boldogan formázza a szöveget, majd elmenti a Drupal a szűrt beviteli formában és egyáltalán nem úgy néz ki a végeredmény mint ahogy azt akarta a felhasználó. Persze Ő soha nem fog rájönni, hogy mimiatt történt, és úgy gondolja valami fejlesztési hiba van a dologban. Természetesen ez nem fejelsztési hiba, de akkor sem várható el egy editortól, hogy rájöjjön mi okozza a problémát.

A Wysiwyg API modul erre talált gyógyírt, mégpedig azzal, hogy az editor használhatóságát a beviteli formához lehet megadni, nem pedig egy user jogosultság. Minden definiált beviteli formához külön-külön más és más editoroka lehet beállítani. Ekkor két kérdés merül fel:

  1. mi az hogy más és más WYSIWYG editorokat lehet rendelni?
  2. bárki használhatja az editort hisz nincs jogosultsághoz kötve?

Az első kérdésre a válasz az, hogy a Wysiwyg API egyszerre több WYSIWYG editort képes kezelni. Ez annyit takar, hogy a beállítási oldalán fel van sorova egy csomó WYSIWYG editor (pl MarkItUp, FCK editor vagy a TinyMCE editor), és amelyik fel van telepítve a megadott könyvtárba azt ki lehet választani egy legördülőből a beviteli formák felsorolása mellett.

A másodikra a válasz: nem. A beviteli formák beállítása oldalán meg lehet adni, hogy egy adott beviteli formát milyen csoport hasznáhat, és tetszőleges számút létrehozhatunk. Tehát pl, ha a Full HTML-t meg akarjuk hagyni a HTML-t értő és WYSIWYG editort nem használó csoport számára, akkor létrehozunk egy új beviteli formát, amelyben szintén nincs semmilyen szürő és hozzárendeljük a kívánt csoporthoz.

Ennyit a Wysiwyg API modul szépségéről, térjünk rá a cikk lényegére, a teaser beállítására.

Miután feltelepítettük a modult észrevehetjük, hogy eltűnt a teaser lehetőségére szolgáló gomb a body rész felett. Ez annyit takar hogy innenstől kezdve magunk nem állíthatjuk be hogy a teaser rész meddig tartson és azt sem, hogy ez akár különbözzön a tartalom többi részétől.

Mivel én hasznosnak találom ezt a funkciót, és többször is szükség volt már rá, ezért elkezdtem kutakodni és utánajárni hogy is lehetne megoldani. A TinyMCE editorban van egy pagebreak plugin, tehát lehetséges hogy megoldódik a probléma, mert a Drupal body részében egy helyre beírjuk a

<!--break-->

kifejezést, akkor ezt a Drupal úgy értelmezi, hogy az előtte lévő rész a teaser rész. Ez eddig szép és jó, csak kéne egy ilyen gomb az editor szerkesztőfelületére.

Ehhez a modul editors/tinymce.inc fájl wysiwyg_tinymce_plugins() fgv-ét kell módosítanunk.
A végén találunk benne egy ilyen részt:

  1.   if ($editor['installed version'] > 3) {
  2.     $plugins['xhtmlxtras'] = array(
  3.       'path' => $editor['library path'] .'/plugins/xhtmlxtras',
  4.       'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted')),
  5.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras',
  6.       'internal' => TRUE,
  7.       'load' => TRUE,
  8.     );
  9.     $plugins['safari'] = array(
  10.       'path' => $editor['library path'] .'/plugins/safari',
  11.       'extensions' => array('safari' => t('Safari compatibility')),
  12.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
  13.       'internal' => TRUE,
  14.       'load' => TRUE,
  15.     );
  16.   }

ezt kell kibővítenünk a következő képpen:

  1.   if ($editor['installed version'] > 3) {
  2.     $plugins['xhtmlxtras'] = array(
  3.       'path' => $editor['library path'] .'/plugins/xhtmlxtras',
  4.       'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted')),
  5.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras',
  6.       'internal' => TRUE,
  7.       'load' => TRUE,
  8.     );
  9.     $plugins['safari'] = array(
  10.       'path' => $editor['library path'] .'/plugins/safari',
  11.       'extensions' => array('safari' => t('Safari compatibility')),
  12.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
  13.       'internal' => TRUE,
  14.       'load' => TRUE,
  15.     );
  16.     $plugins['pagebreak'] = array(
  17.       'path' => $editor['library path'] .'/plugins/pagebreak',
  18.       'buttons' => array('pagebreak' => t('Drupal Teaser')),
  19.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak',
  20.       'internal' => TRUE,
  21.       'load' => TRUE,
  22.     );
  23.   }

Ekkor a TinyMCE editor beállítási oldalán a gombok között megjelenik a Drupal Teaser gomb. Ha kiválasztjuk, akkor a tartalom szerkesztésénél láthatjuk, hogy megjelenik és ha megnyomjuk megjelenik a

<--! pagebreak -->

felirat a szövegben. Várjuk a csodát, hogy majd biztos jó lesz, de sajnos nem működik. Ennek az az oka, hogy a TinyMCE editor pagebreak plugin-je a következőt helyettesíti be:

<--! pagebreak -->

holott a Drupal ezt tudja értelmezni:

<!--break-->

Ebből látszik, hogy a plugin JavaScript fájljába is bele kell kontárkodnunk egy kicsit, mégpedig a modul könyvtárán belül a tinymce/jscripts/tiny_mce/plugins/pagebreak/editor_plugin.js fájlba. Keressük meg a részt, ami beleteszi a kifejezést:

"pagebreak_separator","<!-- pagebreak -->"

és írjuk át a megfelelőre:

"pagebreak_separator","<!---break-->"

Ekkor ha újra megpróbáljuk beállítani, hogy meddig tartson a teaser rész, tökéletesen működni fog.

Mostmár csak egy dolgot akarunk megoldani, mégpedig hogy a teaser rész különbözhessen a tartalomtól.

Ennek megoldására a wysiwyg.module wysiwyg_form_alter() (hook_form_alter()) fgv-ét kell módosítanunk.
Ha megnézzük az egész fgv. csak ennyitből áll:

  1. function wysiwyg_form_alter(&$form, &$form_state) {
  2.   $form['#after_build'][] = 'wysiwyg_process_form';
  3.   // Disable 'teaser' textarea.
  4.   if (isset($form['body_field'])) {
  5.     unset($form['body_field']['teaser_js']);
  6.     $form['body_field']['teaser_include'] = array();
  7.   }
  8. }

Kommentbe benne is van, hogy a teaser részt kiszedi a modul. A teaser_js részt megszűnteti, ez jó is nekünk így, mert egyrészt összeakad a modullal a js (ez az, ha rányomunk a gombra akkor DHTML-el felkerül egy újabb textarea amelyben megadhatjuk a teaser rész tartalmát), másrészt pedig a WYSIWYG editorral már meg tudjuk oldali a teaser elkülönítését, és így legalább szerkeszthető is (úgy értem a WYSIWYG editorral). Nekünk gyakorlatilag csak az “Összefoglaló megjelenítése a teljes nézetben is” checkbox kellene, hogy meg tudjuk adni a teaser beépüljön a tartalomba, vagy különböző legyen.

Ezt úgy tudjuk elérni kiszedjük (vagy kikommentezzük) a következő részt:

  1. $form['body_field']['teaser_include'] = array();

Így a wysiwyg_form_alter() fgv. a követekző képpen módosul:

  1. function wysiwyg_form_alter(&$form, &$form_state) {
  2.   $form['#after_build'][] = 'wysiwyg_process_form';
  3.   // Disable 'teaser' textarea.
  4.   if (isset($form['body_field'])) {
  5.     unset($form['body_field']['teaser_js']);
  6.     //$form['body_field']['teaser_include'] = array();
  7.   }
  8. }

Ekkor szépen megjelenik a checkbox, és beállíthajuk a teaser-t is.

Nekem már többször előfordult olyan problémám, hogy a Drupal elsődleges menüjének (primary links) egyedi kinézete miatt, kizárólag képekből álló menüt kellett készítenem. Ezalatt azt értem, hogy nincs a háttérkép felett semmilyen szöveg, a menü címe is a képen szerepel.

A háttérképeket egyszerűen megadhatjuk, a menüpontok egyedi azonosítója miatt (pl. menu-130 stb.), viszont a rajta lévő szöveget el kell tüntetni valahogy.

CSS-ben ha levesszük a betűméretet 0px-re sajnos nem a megfelelő megoldás, mert IE-ben, Chrome-ban és Operában kicsiben, de látszik.

Egy megoldás, ha a hyperlinkek szövegét span elemek közé tesszük, és a span elemeket tüntetjük el CSS-ben a display: none; definícióval, tehát a cél az lenne, hogy így nézzen ki egy menüelem:

  1. <li class="menu-129"><a class="menu-129" href="/"><span class="primary-title">Nyitólap</span></a></li>

Ekkor minden további nélkül megadhatjuk CSS-ben a következőt:

  1. ul.primary-links span.primary-title {
  2. display: none;
  3. }

Igenám, de hogy érjük el, hogy a Drupal span elemek közé tegye a linkek címét?

A megoldás az, hogy a sminkünk template.php fájljába kifejtjük a theme_links() fgv-t.

Íme a kód:

  1. /**
  2.  * Csak kepekbol allo menu elkeszitesenek alapjai
  3.  *
  4.  * @param unknown_type $links
  5.  * @param unknown_type $attributes
  6.  * @return HTML
  7.  */
  8. function phptemplate_links($links, $attributes = array('class' => 'links')) {
  9.   global $language;
  10.   $output = '';
  11.  
  12.   if (count($links) > 0) {
  13.     $output = '<ul'. drupal_attributes($attributes) .'>';
  14.  
  15.     $num_links = count($links);
  16.     $i = 1;
  17.  
  18.     foreach ($links as $key => $link) {
  19.       $class = $key;
  20.  
  21.       // Add first, last and active classes to the list of links to help out themers.
  22.       if ($i == 1) {
  23.         $class .= ' first';
  24.       }
  25.       if ($i == $num_links) {
  26.         $class .= ' last';
  27.       }
  28.       if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))
  29.           && (empty($link['language']) || $link['language']->language == $language->language)) {
  30.         $class .= ' active';
  31.       }
  32.       $output .= '<li'. drupal_attributes(array('class' => $class)) .'>';
  33.      
  34.       $link['attributes'] = array('class' => $class);
  35.  
  36.       if (isset($link['href'])) {
  37.         // Pass in $link as $options, they share the same keys.
  38.         $link['html'] = true;
  39.         $output .= l('<span class="primary-title">' . $link['title'] . '</span>', $link['href'], $link);
  40.       }
  41.       else if (!empty($link['title'])) {
  42.         // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
  43.         if (empty($link['html'])) {
  44.           $link['title'] = check_plain($link['title']);
  45.         }
  46.         $span_attributes = '';
  47.         if (isset($link['attributes'])) {
  48.           $span_attributes = drupal_attributes($link['attributes']);
  49.         }
  50.         $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
  51.       }
  52.  
  53.       $i++;
  54.       $output .= "</li>\n";
  55.     }
  56.  
  57.     $output .= '</ul>';
  58.   }
  59.  
  60.   return $output;
  61.  
  62. }

Ez a kód, majdnem megegyezik az API oldalon található fgv. kóddal. Nézzük az eltéréseket:

  1. $link['attributes'] = array('class' => $class);

Ezzel annyit érünk el, hogy ne csak a listaelemnek (li) legyen egyedi azonosítója, hanem a hyperlink-nek (a) is. Ez azért fontos, hogy a :hover eseményre is tudjunk képet rakni, ha esetleg különbözik.

  1. $link['html'] = true;
  2. $output .= l('<span class="primary-title">' . $link['title'] . '</span>', $link['href'], $link);

A link tömb lesz az l() fgv. harmadik paramétere, ebbe kell szerepelnie a beállításoknak, és mivel HTML-t adunk meg, ezért a $link tömb html kulcsát igaz értékre kell állítanunk. Az l() fgv. első paraméterének pedig beírjuk a span elemek közé a link címét.

Ha ezt elvégeztük, feltöltjük a template.php-t a smink könyvtárába, majd hogy a rendszer érzékelje az új fgv. létrejöttét, menjünk be a sminkek beállítási oldalába (admin/build/themes) és nyomjunk egy mentést.

Ezután minden menüelem egyedi azonosítoval fog rendelkezni, és a menü címe span elemek között lesz, amit könnyedén eltüntetetünk. Innenstől kezdve csak CSS kérédése az egész.

Az egyik általam készített Drupal alapú webáruház termékeinek adminisztrálása CSV alapon működik. Ez annyit jelent, hogy van egy exportáló felület, ahol a site admin lementheti a termékeit egy csv fájlba, amit aztán valamilyen csv-t kezelő programban (általában Microsoft Excel) átszerkeszt, és egy importáló felületen feltölt. Ezután az oldal feldolgozza és érvénybe lépnek a változtatások.

Ez így mind szép és jó, csak egy gond van vele. A windows felhasználók nagy része, ISO-8859-2-es karakterkódolást használ, míg a Drupal UTF-8-at. Hogy a címek és egyéb karakterkódolástól függő érték problémáját kiküszöböljük, szükség van az mb_convert_encoding() fgv-re, amely segítségével az ISO-8859-2 karakterkódolású részeket, át tudjuk konvertálni a Drupal-nak is használható UTF-8-as karakterkódolásba.

Egyszercsak jelentkezett egy probléma. Azon kezdőbetűk amelyek ékezetesek egyszerűen elvesztek. Tehát például az “Öntapadó” szóból, “ntapadó” lett.
Elkezdtem debuggolni, és azt vettem észre, hogy a feldolgozásra használt beépített fgetcsv() PHP fgv. hibásan működik az ISO-8859-2-es karakterkódólásű fájlok feldolgozásában. A probléma az volt, hogy már a beolvasáskor elvesztek ez ékezetes kezdőbetűk, tehát még az átkonverálás előtt. Ha pedig már akkor elveszett, akkor utólag már nem lehet neki mit csinálni.

Úgyhogy az explode() fgv-t segítségül hívva írtam egy saját, egyszerűsített csv feldolgozó fgv-t, ami erre a célra pont alkalmas:

  1. <?php
  2. function fgetcsv_iso_8859_2($handle, $length, $separator) {
  3.  
  4.   if (!feof($handle)) {
  5.     return explode($separator, fgets($handle, $length));
  6.   } else {
  7.     return false;
  8.   }
  9.  
  10. }
  11. ?>

Persze ez így nem kezeli azon csv fájlokat, amikben mondjuk a mezők értékei ” (macskaköröm) között helyezkednek el, ám az én esetemben erre nem volt szükség. Ha mégis kellene, ki lehet egészíteni a fgv-t:

  1. <?php
  2. function fgetcsv_iso_8859_2($handle, $length, $separator) {
  3.   $enclosed = '"';
  4.   $out      = array();
  5.  
  6.   if (!feof($handle)) {
  7.     $fields = explode($separator, fgets($handle, $length));
  8.     foreach ($fields as $field_data) {
  9.       if (preg_match("/^\\$enclosed/i", $field_data)) {
  10.         $field_data = preg_replace("/(^\\$enclosed|\\$enclosed$)/i","", $field_data);
  11.       }
  12.       $out[] = $field_data;
  13.     }
  14.     return $out;
  15.   } else {
  16.     return false;
  17.   }
  18.  
  19. }
  20. ?>

A Drupal 5-ben van egy hiba amiről a programozók sokszor elfelejtkeznek.

A $node objektumnak van egy path attribútuma:

  1. <?php
  2. $node->path;
  3. ?>

Ez a node-hoz tartozó url alias, tehát a megadott elérési címet tartalmazza. Sokszor használjuk különböző tartalmak dinamikus gyűjtőoldalán, ezért fontos tisztában lennünk ezzel a hibával.

A probléma az, hogy amikor admin-ként vagyunk bejelentkezve a path érték megfelelő, ám ha nem akkor üres, így minden link a kezdőoldalra mutat. Ez annyit takar, hogy nem lehet elérni a kívánt bejegyzéseket, és ha nem elég körültekintő a tesztelés, akkor fel sem tűnik, mivel a session-be bentmaradunk mint bejelentkezett admin-ok, így nem is érzékeljük a hibát.

A megoldás egy egyszerű beépített Drupal fgv használata

  1. <?php
  2. $node->path = drupal_get_path_alias("node/$node->nid");
  3. ?>

Ez egy nagyon egyszerű megoldás, mégis ha nem vagyunk vele tisztába komoly gondot okozhat a weboldalon, de hangsúlyozom: megfelelő tesztelés esetén ki kell hogy derüljön még az élesítés előtt.

A Drupal-nak van egy globális változója, melynek a funkicója az, hogy visszadja azt az URL címet ahol a telepített CMS-ünk aktuálisan el van helyezve.
Ennek a lényege az, hogy pl. ha a fejlesztés egy már meglévő domain egy alkönyvtárában zajlik, akkor a site élesítésekor ne történjen semmilyen probléma. Gyakorlatilag a telepített Drupal-t akárhova pakolhatjuk, a képek elérései és az oldal elérések nem változnak.

Ez a változo a

  1. <?php
  2. $GLOBALS['base_url'];
  3. ?>

Ha ezt használjuk akkor mondjuk egy kép, vagy egy oldal eléréshez nem kell beledrótóznunk a fejlesztéshez használt alkönyvtár nevét.
Ha a Drupal pl. a root/ujweboldal könyvtárban van, akkor ha egy oldal elérését így adjuk meg:

  1. <a href="/kapcsolat">Kapcsolat</a>

akkor nem a root/ujweboldal/kapcsolat oldal fog bejönni, hanem a root/kapcsolat. Ez pedig komoly problémékat okozhat.

Ezt elkerülendően általában a következő eljárást szoktuk alkalmazni:

  1. <a href="<?php print $GLOBALS['base_url'] ?>/kapcsolat">Kapcsolat</a>

Ekkor akárhova helyezzük az oldalt müködni fog az elérés.

Viszont felmerült egy probléma ha az oldalt át akarjuk irányítani a www nélküli címéről a www-s címére.
Ez azt jelenti, hogy beállítunk egy olyan redirect-et, hogy a http://example.com automatikusan menjen át a http://www.example.com címre.

A probléma a form-ok feldolgozásánál jelentkezik!

  1. <form action="<?php print $GLOBALS['base_url'] ?>/form-feldolgozas" method="post"></form>

A jelenség az, hogy a felhasználó megérkezik az oldalra és kitölti a űrlapot, majd a submit eseményt követően nem történik meg a mentés mert az adatok nincsenek benne a $_POST tömbbe. Elkedztem újra végignézni a folyamatot, hogy hol vesznek az értékek.

Észrevettem, hogy a form action paraméterében a $GLOBALS['base_url'] változó miatt az URL www nélküli címe szerepel, emiatt a $_POST adatok a domain www nélküli oldalára mennek át.
Ekkor a következő történik: az action meghívja a www nélküli oldal elérését, a redirect továbbdobja a www-s címre és közeben már nem szerepelnek az adatok a $_POST tömbben, mivel a síma, www nélküli elérésre lettek elküldve. Tehát a redirect miatt elvesztek.

A megoldás az, hogy a form-ok action paraméterében, nem a $_GOLBALS['base_url'] változót adjuk meg, hanem a Drupal beépített url() fgv-ét, mely nem a teljes URL címet adja vissza, hanem a telepített Drupal root könyvtárát.

Tehát a $_GLOBALS['base_url'] helyett a form-ban a következőt alkalmazzuk:

  1. <form action="<?php print url('form-feldolgozas') ?>" method="post"></form>

Hogy szemléltessem a kettő közötti különbséget hasonlítsuk össze a két kód HTML kimenetét:

  1. <form action="<?php print $GLOBALS['base_url'] ?>/form-feldolgozas" method="post"></form>
  2. <form action="<?php print url('form-feldolgozas') ?>" method="post"></form>
  1. <form action="http://example.com/ujweboldal/form-feldolgozas" method="post"></form>
  2. <form action="/ujweboldal/form-feldolgozas" method="post"></form>

Olyan oldalaknál ahol egy bizonyos node tipushoz sok beviteli mező tartozik elvárás lehet, hogy ne kelljen minden módosításnál lemenni a lap aljára.

A logikus megoldás az, hogy az ENTER gomb lenyomásával jelzi a felhasználó, hogy végzett a tartalom módosításával, feltöltésével.

Az eseményt a jQuery JavaScript könyvtárral könnyen meg lehet oldalni, utána pedig már csak egy nagyon egyszerű Drupal modult kell hozzá írni, és minden olyan weblapon alkalmazni lehet ahol szükség van rá.

Íme a JavaScript fájl amint majd hozzárendelünk az oldalhoz:

  1. var writing   = false;
  2. var node_edit = false;
  3.  
  4. $(document).ready(function() {
  5.  
  6.   if ($('#node-form').length) {
  7.     node_edit = true;
  8.   }
  9.  
  10.   if (node_edit) {
  11.     $('.form-textarea', $('#node-form').parent()).focus(function() {
  12.       writing = true;
  13.     });
  14.     $('.form-textarea', $('#node-form').parent()).blur(function() {
  15.       writing = false;
  16.     });
  17.   }
  18. });
  19.  
  20. $(document).keypress(function(event) {
  21.   if (event.keyCode == 13 && !writing && node_edit) {
  22.  
  23.     if (confirm_submit()) {
  24.       $('#edit-submit', $('#node-form').parent()).click();
  25.     }
  26.   }
  27. });
  28.  
  29. function confirm_submit() {
  30.   var agree = confirm("Valóban menti a tartalom változásait?");
  31.   if (agree) {
  32.     return true ;
  33.   } else {
  34.     return false ;
  35.   }
  36. }

Menjünk végig a kódon, mi miért van!

Először is a writing változó, a node_edit változó és az oldal betöltésekor lefutó fgv-ek, vagyis a $(document).ready(function()).
A writing változó azért fontos, mert az ENTER lenyomása esetén nem feltétlenül a tartalom szerkesztésének befejeztét jelzi a felhasználó, hanem esetlegesen éppen egy olyan szöveget gépel be, amiben van sortörés. Az pedig elég nagy gond lenne, ha nem lehetne több sort bevinni egy node-ba :-)
Ezért aztán létrehozzuk a writing bool tipusú változót alapértelmezetten hamisra állítva, és figyelve az eseményeket változtatjuk, hogy éppen gépelés történik vagy sem.
Az odlal betöltődésekor a form-textarea osztályokhoz hozzárendeljük a focus() és blur() fgv-eket.
Amikor egy textarea-ba bekattint (focus) a felhasználó a writing változó értéke igazra változik, amikor elhagyja (blur) hamisat vesz fesz vel.
A node_edit bool tipusú változót azért hozzuk létre, mert a későbbiekben a hook_form_alter()-t fogjuk használni a js beépítésére, ez azonban nem csak a node-ok szerkesztésénél fut le, hanem például a felhasználók kezelési felületén is. Ezért megvizsgáljuk, hogy a node-form létezik-e, és csak akkor futtatjuk a kódót ha igen.

A billentyű lenyomása, a keypress(function).
Ezt magához az oldalhoz ($(document)) kell hozzárendelni, hiszen nem valamely beviteli mezőt figyeljük.
A fgv paramétere az event változó, ez tartalmazza a billentyűlenyomás tulajdonságait.
A keyCode a lenyomott billentyű ID-ját tartalmazza ami egy egész. Az ENTER gomb kulcsa a 13-as szám.
Emiatt a feltételben megvizsgáljuk, hogy a 13-as kódszámmal rendelkező billentyű lett-e lenyomva (ENTER), és hogy éppen folyik-e a gépelés (writing változó).
Ha a feltételnek megfelelőek az értékek és a felhasználó megerősítette (confirm_submit), hogy befelyezte a szerkesztést akkor végrehajtjuk a node mentését.

Lehetséges, hogy a

  1. $('#edit-submit', $('#node-form').parent()).click();

sor egy kis magyarázatra szorul.

Az node-form a form id attributuma.
Gondolhatnánk a legegyszerűbb meghívni a submit eseményt a submit() fgv-el. A probléma az, hogy a node szerkesztési felületén három submit gomb van: beküldés, előnézet, törlés. Emiatt a submit esemény nem egyértelmű, hiszen mind a három gomb, ugyanannak a form-nak a submit gombjai, és mindegyik mást csinál. Emiatt nem történik semmi.
A következő gondolat az, hogy oldajuk meg a problémát az edit-submit id-jú gomb click esemény meghívásával.
Ezzel a gond az, hogy a Drupal beépített form-jainak mind ez az azonosítója. Így pl ha engedélyezve van a search modul akkor annak a submit gombaja is ugyanezzel az id-val rendelkezik, tehát könnyen előfordulhat, hogy az enter lenyomása mondjuk keresést eredményez, nem pedig a tartalom elmentését.
Ezért magában a form-ban kell megkeresnünk a megfelelő gombot, viszont form-on belül nem lehet html objektumot keresni, ezért a form-ot körülölelő div-ben kell megtennünk.

Az utolső fgv. a megerősítés, vagyis rákérdez a felhasználótól, hogy valóban mentsük-e a tartalmat.

Ez a jQuery megvalósítás működik, mostmár csak be kell építeni a site-ba.

Mivel ennek a JavaScript-nek csak és kizárólag a node szerkeztési felületén kell megjelnnie, így a hook_form_alter()-t a legérdemesebb használni.

A modulunk, tehát az .info fájlon kívül ennyi:

  1. <?php
  2. /**
  3.  * hook_form_alter().
  4.  */
  5. function enter_node_submit_form_alter($form_id, &$form) {
  6.   drupal_add_js(drupal_get_path('module', 'enter_node_submit') .'/enter_node_submit.js');
  7. }
  8. ?>

A modul letölthető innen:
enter_node_submit.module

Egyik ügyfelünknél felmerült az igény, hogy egy form kitöltése után automatikusan küldjünk ki egy email-t csatolmánnyal együtt.

A probléma megoldására a régebbről ismert HtmlMimeMail PHP osztályt használtam.

Nagyon jól megszerkesztett osztály úgyhogy nagyon könnyű használni, ám Drupal alatt mégsem lehetett alkalmazni a leírásban olvastakat.

Az első problémát az jelentette, hogy nem lehetett az osztályt beilleszteni, illetve objektumot készíteni belőle. Egyszerűen kifagyott tőle a Drupal és fehér képernyő jelent meg. Szerencsére van olyan verziója is, amitől nem jelentkezik ez a probléma.

Innen telölthető zip-el tömörítve (20 KB): HtmlMimeMail Drupal.

Miután ez megoldódott nagyjából a HtmlMimeMail oldalán található leírásokat kellett alkalmazni néhány dolgot kivéve.

Először egy ellenőrzés arról, hogy nem-e létezik már az osztály:

  1.       if (!class_exists('htmlMimeMail') && !class_exists('Mail_mimePart')) {
  2.         require_once(dirname(__FILE__)."/htmlmimemail/htmlMimeMail.php");
  3.       }

Utána a szokásos eljárást követve létrehozzuk az objektumot, és beállítjuk a karakterkódolást is, hiszen a Drupal UTF-8 -at használ

  1.       $mail = new htmlMimeMail();
  2.       $mail->setTextCharset('UTF-8');
  3.       $mail->setHtmlCharset('UTF-8');
  4.       $mail->setHeadCharset('UTF-8');

A setFrom() setSubject() és a setText() vagy setHTML() beállítása ugynúgy történik mint általában:

  1.       $mail->setFrom($_POST["offer_name"] . " <" . $_POST["offer_email"] . ">");
  2.       $mail->setSubject('Ezt nézd meg');
  3.       $mail->setText($uzenet);   // Ha text levelt kuldunk
  4.       $mail->setHTML($uzenet); // Ha HTML levelet kuldunk

Na ezután jöhetett a csatolmány hozzárendelése! A HtmlMimeMail weboldalán a következőt olvashatjuk:

  1. $mail->addAttachment(new fileAttachment('example.zip', 'application/zip', new Base64Encoding()));

Ez sajnos hibát generál, mivel nem létezik a FileAttachement osztály, így nincs miből objektumot létrehozni.
A megoldást a következő eljárás jelentette:

  1.       $filename = $node->field_nyitolap_top10_latvany[0]['filepath'];
  2.       $attachment = '';
  3.  
  4.       if ($fp = fopen($filename, 'rb')) {
  5.         while (!feof($fp)) {
  6.           $attachment .= fread($fp, filesize($filename));
  7.         }
  8.       }
  9.       fclose($fp);

Így a csatolmány egyből bekerült a levélbe!

Végül be kell állítani a fejlécet és elküldeni:

  1.       $mail->setHeader('Date',date('D, d M y H:i:s 0'));
  2.       $mail->setHeader('X-Mailer','Drupal');
  3.       $mail->send(array('<' . $_POST["offered_email_$i"] . '>'),'mail');

És tökéletesen működik a csatolmány elküldése Drupal alatt.

Biztos sokakban felmerült már a kérdés, hogy hogyan lehet olyan hyperlink-et készíteni ami új ablakban nyílik meg, mégis valid.

Ugyebár megvan a lehetőség HTML segítségével új ablakba nyíló hivatkozás készítésére, de sajnos a hyperlink-nek nincs valid target attributuma.

A kellő eredményt hozza a következő kódrészlet, de nem sajnos nem lesz valid:

  1. <a href="http://drupal.org/" title="http://drupal.org/" target="_blank">drupal.org</a>

Én a megoldásra a jQuery JavaScript könyvtárat használom. Ez amúgy alapól benne van a Drupal CMS-ben. Egy nagyon egyszerű eljárást alkalmazok:
Azokat a hyperlink-ekekt amiket új ablak szeretnénk megnyitni elhelyezem a new_window osztályba, és az oldal betöltődésekor a jQuery segítségével helyettesíthetem a target=”_blank” paramétert.

  1. <script type="text/javascript">
  2. $(document).ready(function() {
  3.   $('a.new_window').click(function(){
  4.     window.open(this.href);
  5.     return false;
  6.   });
  7. });
  8. </script>
  9.  
  10. <a class="new_window" href="http://drupal.org/" title="http://drupal.org/">drupal.org</a>

Drupal 6 Internet Corporation theme: Letöltés / Download

Mivel még nem sok Drupal 6 smink készült el és nem akartam a Drupal 6 alapértelmezett sminkjét felrakni az oldalamra úgy döntöttem megpróbálok egy Drupal 5-öst átírni, hogy működjön.

Az Internet Corporation theme mellett döntöttem.

Szerencsére felkerült a Drupal oldalára egy dokumentáció a Drupal 6 sminkeléséről
Ez volt a kiinduló alapom, hogy mégis mért nem müködnek a Drupal 5-ös sminkek a 6-os verzió alatt.

Bevallom őszintén nagyobb problémára gondoltam mint amekkora ténylegesen volt. Az egész megvalósítás nem vett igénybe fél óránál többet, persze miután elolvastam a Drupal oldalán a dokumentációt.

Lényeges különbség, hogy kell egy info fájl, mint a moduloknál. Ebbe kell egy a sminkkel kapcsolatos dolgokat deffiniálni. Milyen css-ek tartoznak hozzá, milyen JavaScript-eket töltösön be automatikusan, milyen régiói vannak stb.

Az Internet_Corporation.info így néz ki, mivel nem igényel különösebb js fájlokat, és a template.php fájlba a régiók definiálva vannak, bár itt is megtehettem volna.

Internet_Corporation.info:

; $Id$
  1. name = Internet Corporation
  2. description = Implements of Internet_Corporation theme for Druapl 6
  3. version = 1.0
  4. core = 6.x
  5. engine = phptemplate
  6. stylesheets[all][] = style.css

Innenstől fogva már nagyjából működött is, persze a blokkok nem jelentek meg, mert megválzotak. Az eredeti Internet Corporation smink nem volt valid HTML úgyhogy eltávolítottam belőle a primary_links-re vontakozó hibát.
A gond az volt, hogy az eredeti Drupal 5-ös sminkbe ez szerepelt:

  1. <ul id="menu">
  2. <?php if (isset($primary_links)) { ?><?php print theme('links', $primary_links, array()) ?><?php } ?>
  3. </ul>

A links_theme ul - li elemeket generál, ezért nem lehet körülvenni egy ul elemmel. Így hát a Drupal 6-os sminkben kicseréltem div-re:

  1. <div id="menu">
  2. <?php if (isset($primary_links)) { ?><?php print theme('links', $primary_links, array()) ?><?php } ?>
  3. </div>

Egy lényeges különség az, hogy mostmár nem a $slidebar_left és $slidebar_right változókkal tudjuk elérni a bal- és jobboldali régiókat hanem a $left és $right változókkal. Így a Drupal 5-ös Internet Corporation theme-ben szereplő

  1. <?php if ($sidebar_left) { ?>
  2.   <div id="left">
  3.     <?php print $sidebar_left ?>      
  4.  </div>
  5. <?php } ?>

kódrészletet ki kellett cserélni Drupal 6 alatt a következőre:

  1. <?php if (!empty($left)) { ?>
  2.   <div id="left">
  3.     <?php print $left ?>      
  4.  </div>
  5. <?php } ?>

Ezután a Drupal 5-ös page.tpl.php-ban szereplö

  1. <?php if ($mission) { ?><div class="mission"><?php print $mission ?></div><?php } ?>

sort ki kellett cserelni a

  1. <?php if (!empty($mission)) { ?><div class="mission"><?php print $mission ?></div><?php } ?>

sorra. Így mér ez is működött Drupal 6 alatt.

Végül eltérés a $footer_message változó hiánya. Ez is megváltozott és a $footer vette át a helyet.

Így a Druapl 5-ös kódban szereplő

  1. <div id="col1"><?php print $footer_message ?></div>

sort egyszerűen kicsereltem a

  1. <div id="col1"><?php print $footer ?></div>

sorra, és innenstől kezdve az Internet Corporation theme, ugyanúgy működött Drupal 6 alatt, mintha Drupal 5-öt használnánk.