root/trunk/lib/SGL/Category.php

Revision 3124, 18.2 kB (checked in by demian, 3 weeks ago)

bugfix to trunk merge -2

  • Property svn:eol-style set to native
Line 
1 <?php
2 /* Reminder: always indent with 4 spaces (no tabs). */
3 // +---------------------------------------------------------------------------+
4 // | Copyright (c) 2006, Demian Turner                                         |
5 // | All rights reserved.                                                      |
6 // |                                                                           |
7 // | Redistribution and use in source and binary forms, with or without        |
8 // | modification, are permitted provided that the following conditions        |
9 // | are met:                                                                  |
10 // |                                                                           |
11 // | o Redistributions of source code must retain the above copyright          |
12 // |   notice, this list of conditions and the following disclaimer.           |
13 // | o Redistributions in binary form must reproduce the above copyright       |
14 // |   notice, this list of conditions and the following disclaimer in the     |
15 // |   documentation and/or other materials provided with the distribution.    |
16 // | o The names of the authors may not be used to endorse or promote          |
17 // |   products derived from this software without specific prior written      |
18 // |   permission.                                                             |
19 // |                                                                           |
20 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       |
21 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         |
22 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR     |
23 // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT      |
24 // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,     |
25 // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT          |
26 // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,     |
27 // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
28 // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
29 // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE     |
30 // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.      |
31 // |                                                                           |
32 // +---------------------------------------------------------------------------+
33 // | Seagull 0.6                                                               |
34 // +---------------------------------------------------------------------------+
35 // | Category.php                                                              |
36 // +---------------------------------------------------------------------------+
37 // | Author:   Demian Turner <demian@phpkitchen.com>                           |
38 // |           Aymerick Jehanne <aymerick@jehanne.org>                         |
39 // +---------------------------------------------------------------------------+
40 // $Id: Category.php,v 1.10 2005/04/27 23:32:41 demian Exp $
41
42 require_once SGL_CORE_DIR. '/NestedSet.php';
43 require_once SGL_MOD_DIR . '/user/classes/UserDAO.php';
44
45 define('SGL_MAX_RECURSION', 100);
46
47 /**
48  * Wrapper to SGL_NestedSet, to manipulate Categories.
49  *
50  * @package SGL
51  * @author  Demian Turner <demian@phpkitchen.com>
52  * @author  Aymerick Jehanne <aymerick@jehanne.org>
53  * @version $Revision: 1.10 $
54  */
55 class SGL_Category
56 {
57     var $_params = array();
58     var $_nestedSetNode = array();
59     var $_da = null;
60
61     /**
62      * Constructor.
63      *
64      * @return void
65      */
66     function SGL_Category()
67     {
68         SGL::logMessage(null, PEAR_LOG_DEBUG);
69
70         $c = &SGL_Config::singleton();
71         $this->conf = $c->getAll();
72
73         $this->da = & UserDAO::singleton();
74         $this->dbh = & SGL_DB::singleton();
75
76         //  Nested Set Params
77         $this->_params = array(
78             'tableStructure' => array(
79                 'category_id' => 'id',
80                 'root_id'     => 'rootid',
81                 'left_id'     => 'l',
82                 'right_id'    => 'r',
83                 'order_id'    => 'norder',
84                 'level_id'    => 'level',
85                 'parent_id'   => 'parent',
86                 'label'       => 'label',
87                 'perms'       => 'perms',
88             ),
89             'tableName'     => $this->conf['table']['category'],
90             /** @todo Use $this->conf['table']['table_lock'] */
91             'lockTableName' => $this->conf['db']['prefix'] . 'table_lock',
92             'sequenceName'  => $this->conf['table']['category']);
93     }
94
95     /**
96      * Create a Category with given values.
97      *
98      * @access  public
99      * @param   array  $values Category values
100      * @return  array          NestedSet node created
101      */
102     function create(&$values)
103     {
104         SGL::logMessage(null, PEAR_LOG_DEBUG);
105
106         //  set default category label if none provided
107         if (!isset($values['label']))
108             $values['label'] = 'New Category';
109
110         //  use a NestedSet
111         $nestedSet = new SGL_NestedSet($this->_params);
112
113         //  create new set with first rootnode
114         if ($values['parent_id'] == 0) {
115             //  we want a root node
116             $this->_nestedSetNode = $nestedSet->createRootNode($values);
117         } elseif ((int)$values['parent_id'] > 0) {
118             //  we want a sub node
119             $this->_nestedSetNode = $nestedSet->createSubNode($values['parent_id'], $values);
120         } else {
121             //  error
122             SGL::raiseError('Incorrect parent node id passed to ' . __CLASS__ . '::' .
123                 __FUNCTION__, SGL_ERROR_INVALIDARGS);
124         }
125
126         //  clear block & category caches
127         SGL_Cache::clear('blocks');
128         SGL_Cache::clear('categorySelect');
129
130         return $this->_nestedSetNode;
131     }
132
133     /**
134      * Update a Category with given values.
135      *
136      * @access  public
137      * @param   int    $category_id Category ID to update
138      * @param   array  $values      Values to set
139      * @return  string              An empty string if error while updating, else
140      *                              a message to display.
141      */
142     function update($category_id, &$values)
143     {
144         $message = '';
145
146         SGL::logMessage(null, PEAR_LOG_DEBUG);
147         $nestedSet = new SGL_NestedSet($this->_params);
148
149         //  attempt to update section values
150         if (!$nestedSet->updateNode($category_id, $values)) {
151             SGL::raiseError('There was a problem updating the record',
152                 SGL_ERROR_NOAFFECTEDROWS);
153         }
154
155         //  move node if needed
156         switch ($values['parent_id']) {
157         case $values['original_parent_id']:
158             //  usual case, no change => do nothing
159             $message = 'Category details successfully updated';
160             break;
161
162         case $values['category_id']:
163             //  cannot be parent to self => display user error
164             $message = 'Category details updated, no data changed';
165             break;
166
167         case 0:
168             //  move the category, make it into a root node, just above it's own root
169             $thisNode = $nestedSet->getNode($values['category_id']);
170             $moveNode = $nestedSet->moveTree($values['category_id'], $thisNode['root_id'], 'BE');
171
172             if (!is_a($thisNode, 'PEAR_Error') && !is_a($moveNode, 'PEAR_Error')) {
173                 $message = 'Category details successfully updated';
174             }
175             break;
176
177         default:
178             //  move the category under the new parent
179             $moveNode = $nestedSet->moveTree($values['category_id'], $values['parent_id'], 'SUB');
180
181             if (!is_a($moveNode, 'PEAR_Error') && !is_a($moveNode, 'PEAR_Error')) {
182                 $message = 'Category details successfully updated';
183             }
184             break;
185         }
186
187         // Update perms
188         require_once SGL_CORE_DIR . '/CategoryPerms.php';
189         $perms = & new SGL_CategoryPerms($category_id);
190         $perms->set('aPerms', $values['perms']);
191         $perms->update();
192
193         //  clear block & category caches
194         SGL_Cache::clear('categorySelect');
195         SGL_Cache::clear('blocks');
196
197         /** @todo Return a constant instead of a message ! */
198         return $message;
199     }
200
201     /**
202      * Delete several Categories.
203      *
204      * @access public
205      * @param  array  $aDelete Array of Category IDs to delete
206      * @return void
207      */
208     function delete(&$aDelete)
209     {
210         SGL::logMessage(null, PEAR_LOG_DEBUG);
211
212         if (is_array($aDelete)) {
213             $nestedSet = new SGL_NestedSet($this->_params);
214             //  deleting parent nodes automatically deletes children nodes, but user
215             //  might have checked child nodes for deletion, in which case deleteNode()
216             //  would try to delete nodes that no longer exist, after parent deletion,
217             //  and therefore error, so test first to make sure they're still around
218             foreach ($aDelete as $index => $categoryId) {
219                 if ($nestedSet->getNode($categoryId)) {
220                     $nestedSet->deleteNode($categoryId);
221                 }
222             }
223         } else {
224             SGL::raiseError("Incorrect parameter passed to " . __CLASS__ . '::' .
225                 __FUNCTION__, SGL_ERROR_INVALIDARGS);
226         }
227
228         //  clear block & category caches
229         SGL_Cache::clear('categorySelect');
230         SGL_Cache::clear('blocks');
231     }
232
233     /**
234      * Move a Category.
235      *
236      * @access  public
237      * @param   int    $category_id Category ID to move
238      * @param   int    $target_id   New parent
239      * @param   int    $pos         New position
240      */
241     function move($category_id, $target_id, $pos)
242     {
243         SGL::logMessage(null, PEAR_LOG_DEBUG);
244         $nestedSet = new SGL_NestedSet($this->_params);
245
246         //  move tree
247         $nestedSet->moveTree($category_id, $target_id, $pos);
248
249         //  clear block & category caches
250         SGL_Cache::clear('categorySelect');
251         SGL_Cache::clear('blocks');
252     }
253
254     /**
255      * Load a Category, given its ID.
256      *
257      * @access  public
258      * @param   int    $category_id Category ID to load
259      * @return  bool                TRUE if loaded, FALSE if error
260      */
261     function load($category_id)
262     {
263         SGL::logMessage(null, PEAR_LOG_DEBUG);
264
265         //  check if category_id not set or 0
266         if (!isset($category_id) || ($category_id == '0')) {
267             return false;
268         }
269
270         //  get NestedSet node
271         $nestedSet = new SGL_NestedSet($this->_params);
272         $this->_nestedSetNode = $nestedSet->getNode($category_id);
273
274         //  check if category_id does not exist
275         if (!isset($this->_nestedSetNode) || empty($this->_nestedSetNode)) {
276             SGL::raiseError('Invalid category ID passed', SGL_ERROR_INVALIDARGS);
277             return false;
278         }
279         return true;
280     }
281
282     /**
283      * Get current Category values.
284      *
285      * Category must be loaded before using this function.
286      *
287      * @access  public
288      * @return  array  NestedSet node representing the current Category
289      */
290     function getValues()
291     {
292         SGL::logMessage(null, PEAR_LOG_DEBUG);
293
294         //  check if Category is not loaded
295         if (!isset($this->_nestedSetNode) || empty($this->_nestedSetNode)) {
296             SGL::raiseError('Category not loaded', SGL_ERROR_INVALIDCALL);
297             return null;
298         }
299
300         return $this->_nestedSetNode;
301     }
302
303     /**
304      * Get current Category permissions.
305      *
306      * Category must be loaded before using this function.
307      *
308      * @access  public
309      * @return  array  Current Category permissions
310      */
311     function getPerms()
312     {
313         SGL::logMessage(null, PEAR_LOG_DEBUG);
314
315         //  check if Category is not loaded
316         if (!isset($this->_nestedSetNode) || empty($this->_nestedSetNode)) {
317             SGL::raiseError('Category not loaded', SGL_ERROR_INVALIDCALL);
318             return null;
319         }
320
321         //  get assoc array of all roles
322         $aRoles = $this->da->getRoles();
323         $aRoles[0] = 'guest';
324
325         //  if no perms in category table for current category_id, set to empty array
326         $aPerms = (isset($this->_nestedSetNode['perms']) && count($this->_nestedSetNode['perms']))
327             ? explode(',', $this->_nestedSetNode['perms'])
328             : array();
329
330         foreach ($aRoles as $roleId => $roleName) {
331             $tmp['role_id'] = $roleId;
332             $tmp['name'] = $roleName;
333             $tmp['isAllowed'] = (!in_array($roleId, $aPerms)) ? 1 : 0;
334             $perms[] = (object)$tmp;
335         }
336
337         return $perms;
338     }
339
340     /**
341      * Returns true if current user has perms to view a category.
342      *
343      * Category must be loaded before using this function.
344      *
345      * @access  public
346      * @return bool
347      */
348     function hasPerms()
349     {
350         //  check if Category is not loaded
351         if (!isset($this->_nestedSetNode) || empty($this->_nestedSetNode)) {
352             return false;
353         }
354         $aPerms = $this->getPerms();
355         $roleId = SGL_Session::get('rid');
356
357         foreach ($aPerms as $perm) {
358             if ($perm->role_id == $roleId) {
359                 return $perm->isAllowed;
360             }
361         }
362         return false;
363     }
364
365     /**
366      * Get a tree representing all Categories.
367      *
368      * @access  public
369      * @return  array  Categories tree
370      */
371     function getTree()
372     {
373         SGL::logMessage(null, PEAR_LOG_DEBUG);
374
375         $nestedSet = new SGL_NestedSet($this->_params);
376         $nestedSet->setImage('folder', 'images/treeNav/file.png');
377         $categoryTree = $nestedSet->getTree();
378         $nestedSet->addImages($categoryTree);
379
380         return $categoryTree;
381     }
382
383     /**
384      * Retrieve children
385      *
386      * @access  public
387      * @param   int     $id
388      * @return  array   categories children
389      */
390     function getChildren($id)
391     {
392         if (!is_numeric($id)) {
393             SGL::raiseError('Wrong datatype passed to '  . __CLASS__ . '::' .
394                 __FUNCTION__, SGL_ERROR_INVALIDARGS, PEAR_ERROR_DIE);
395         }
396         $query = "  SELECT category_id, label
397                     FROM " . $this->conf['table']['category'] . "
398                     WHERE parent_id = $id
399                     ORDER BY parent_id, order_id";
400
401         $result = $this->dbh->query($query);
402         $count = 0;
403         $aChildren = array();
404         while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
405             $aChildren[$count]['category_id'] = $row['category_id'];
406             $aChildren[$count]['label'] = $row['label'];
407             $count++;
408         }
409
410         return $aChildren;
411     }
412
413     /**
414      * Checks if an category is a branch
415      *
416      * @access  public
417      * @param   int     $id
418      * @return  boolean
419      */
420     function isBranch($id)
421     {
422         $nestedSet = new SGL_NestedSet($this->_params);
423         $ns = $nestedSet->_getNestedSet();
424         $node = $ns->pickNode($id, $keepAsArray = true, $alias = true);
425         if ($node) {
426             if (($node['r'] - $node['l']) > 1) {
427                 return true;
428             }
429         }
430         return false;
431     }
432
433     /**
434      *  Generates breadcrumbs for category
435      *
436      * @access  public
437      * @param   integer $category_id
438      * @param   boolean $links          build links
439      * @param   string  $style          CSS Class
440      * @param   boolean $links          add link to the current CatID
441      * @return  string  $finalHtmlString
442      */
443     function getBreadCrumbs($category_id, $links = true, $style = '', $lastLink = false)
444     {
445         SGL::logMessage(null, PEAR_LOG_DEBUG);
446         if (!is_numeric($category_id)) {
447             SGL::raiseError("Invalid category ID, '$category_id', passed to " .
448                 __CLASS__ . '::' . __FUNCTION__, SGL_ERROR_INVALIDARGS);
449             return false;
450         }
451         $nestedSet = new SGL_NestedSet($this->_params);
452         $node = $nestedSet->getNode($category_id);
453
454         if (empty($node) || is_a($node, 'PEAR_Error')) {
455             return false;
456         }
457         $crumbs = $nestedSet->getBreadcrumbs($category_id);
458         $htmlString = '';
459
460
461         $req = & SGL_Request::singleton();
462
463         //  logical case for publisher->articleview->view
464         $managerName = $req->get('managerName');
465         $action = $req->get('action');
466
467         //  Make sure the articleview->view page shows the right breadcrumbs
468         if (strtolower($managerName) == 'articleview' &&
469             strtolower($action) == 'view') {
470             // summary is the correct action when browsing categories
471             $action = "summary";
472         }
473
474         //  build url for current page
475         $url = SGL_Url::makeLink$action,
476                                    $managerName,
477                                    $req->get('moduleName')
478                                    );
479         $url .= 'frmCatID/';
480
481         foreach ($crumbs as $crumb) {
482             if ($links) {
483                 $htmlString .= "<a class='$style' href='$url".$crumb['category_id']."/'>" .
484                     stripslashes($crumb['label']) . "</a> > ";
485             } else {
486                 $htmlString .= stripslashes($crumb['label']) . " > ";
487             }
488         }
489         $finalHtmlString = ($lastLink)
490             ? $htmlString . "<a class='$style' href='$url".$category_id."/'>" . $node['label'] ."</a>"
491             : $htmlString . $node['label'];
492         return $finalHtmlString;
493     }
494
495     /**
496      * Retrieves category label
497      *
498      * @access  public
499      * @param   int     $id
500      * @return  string
501      */
502     function getLabel($id)
503     {
504         $nestedSet = new SGL_NestedSet($this->_params);
505         $node = $nestedSet->getNode($id);
506         if (!PEAR::isError($node)) {
507             return $node['label'];
508         } else {
509             return false;
510         }
511     }
512
513     function debug($id = 0)
514     {
515         SGL::logMessage(null, PEAR_LOG_DEBUG);
516         $result = $this->getChildren($id);
517         $listString .= '<ul>';
518         for ($x = 0; $x < count($result); $x++) {
519             $listString .= "<li>" . $result[$x]["label"] . "[" . $result[$x]['category_id'] . "]";
520
521             // if branch then recurse
522             if ($this->isBranch($result[$x]['category_id'])) {
523                 $listString .= $this->debug($result[$x]['category_id']);
524             }
525         }
526         $listString .=  '</ul>';
527         return $listString;
528     }
529
530     //  abstract methods
531     function render()
532     {
533         //  abstract
534     }
535 }
536 ?>
Note: See TracBrowser for help on using the browser.