OSDN Git Service

Some minor PHP 5.3 compatibility fixes
[nucleus-jp/nucleus-jp-ancient.git] / utf8 / nucleus / libs / MANAGER.php
1 <?php
2 /*
3  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4  * Copyright (C) 2002-2009 The Nucleus Group
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * (see nucleus/documentation/index.html#license for more info)
11  */
12 /**
13  * This class makes sure each item/weblog/comment object gets requested from
14  * the database only once, by keeping them in a cache. The class also acts as
15  * a dynamic classloader, loading classes _only_ when they are first needed,
16  * hoping to diminish execution time
17  *
18  * The class is a singleton, meaning that there will be only one object of it
19  * active at all times. The object can be requested using MANAGER::instance()
20  *
21  * @license http://nucleuscms.org/license.txt GNU General Public License
22  * @copyright Copyright (C) 2002-2009 The Nucleus Group
23  * @version $Id$
24  * $NucleusJP: MANAGER.php,v 1.8.2.1 2007/09/05 07:00:18 kimitake Exp $
25  */
26 class MANAGER {
27
28     /**
29      * Cached ITEM, BLOG, PLUGIN, KARMA and MEMBER objects. When these objects are requested
30      * through the global $manager object (getItem, getBlog, ...), only the first call
31      * will create an object. Subsequent calls will return the same object.
32      *
33      * The $items, $blogs, ... arrays map an id to an object (for plugins, the name is used
34      * rather than an ID)
35      */
36     var $items;
37     var $blogs;
38     var $plugins;
39     var $karma;
40     var $templates;
41     var $members;
42
43     /**
44      * cachedInfo to avoid repeated SQL queries (see pidInstalled/pluginInstalled/getPidFromName)
45      * e.g. which plugins exists?
46      *
47      * $cachedInfo['installedPlugins'] = array($pid -> $name)
48      */
49     var $cachedInfo;
50
51     /**
52       * The plugin subscriptionlist
53       *
54       * The subcription array has the following structure
55       *     $subscriptions[$EventName] = array containing names of plugin classes to be
56       *                                  notified when that event happens
57       */
58     var $subscriptions;
59
60     /**
61       * Returns the only instance of this class. Creates the instance if it
62       * does not yet exists. Users should use this function as
63       * $manager =& MANAGER::instance(); to get a reference to the object
64       * instead of a copy
65       */
66     function &instance() {
67         static $instance = array();
68         if (empty($instance)) {
69             $instance[0] =& new MANAGER();
70         }
71         return $instance[0];
72     }
73
74     /**
75       * The constructor of this class initializes the object caches
76       */
77     function MANAGER() {
78         $this->items = array();
79         $this->blogs = array();
80         $this->plugins = array();
81         $this->karma = array();
82         $this->parserPrefs = array();
83         $this->cachedInfo = array();
84     }
85
86     /**
87       * Returns the requested item object. If it is not in the cache, it will
88       * first be loaded and then placed in the cache.
89       * Intended use: $item =& $manager->getItem(1234)
90       */
91     function &getItem($itemid, $allowdraft, $allowfuture) {
92         $item =& $this->items[$itemid];
93
94         // check the draft and future rules if the item was already cached
95         if ($item) {
96             if ((!$allowdraft) && ($item['draft']))
97                 return 0;
98
99             $blog =& $this->getBlog(getBlogIDFromItemID($itemid));
100             if ((!$allowfuture) && ($item['timestamp'] > $blog->getCorrectTime()))
101                 return 0;
102         }
103         if (!$item) {
104             // load class if needed
105             $this->loadClass('ITEM');
106             // load item object
107             $item = ITEM::getitem($itemid, $allowdraft, $allowfuture);
108             $this->items[$itemid] = $item;
109         }
110         return $item;
111     }
112
113     /**
114       * Loads a class if it has not yet been loaded
115       */
116     function loadClass($name) {
117         $this->_loadClass($name, $name . '.php');
118     }
119
120     /**
121       * Checks if an item exists
122       */
123     function existsItem($id,$future,$draft) {
124         $this->_loadClass('ITEM','ITEM.php');
125         return ITEM::exists($id,$future,$draft);
126     }
127
128     /**
129       * Checks if a category exists
130       */
131     function existsCategory($id) {
132         return (quickQuery('SELECT COUNT(*) as result FROM '.sql_table('category').' WHERE catid='.intval($id)) > 0);
133     }
134
135     /**
136       * Returns the blog object for a given blogid
137       */
138     function &getBlog($blogid) {
139         $blog =& $this->blogs[$blogid];
140
141         if (!$blog) {
142             // load class if needed
143             $this->_loadClass('BLOG','BLOG.php');
144             // load blog object
145             $blog =& new BLOG($blogid);
146             $this->blogs[$blogid] =& $blog;
147         }
148         return $blog;
149     }
150
151     /**
152       * Checks if a blog exists
153       */
154     function existsBlog($name) {
155         $this->_loadClass('BLOG','BLOG.php');
156         return BLOG::exists($name);
157     }
158
159     /**
160       * Checks if a blog id exists
161       */
162     function existsBlogID($id) {
163         $this->_loadClass('BLOG','BLOG.php');
164         return BLOG::existsID($id);
165     }
166
167     /**
168      * Returns a previously read template
169      */
170     function &getTemplate($templateName) {
171         $template =& $this->templates[$templateName];
172
173         if (!$template) {
174             $template = TEMPLATE::read($templateName);
175             $this->templates[$templateName] =& $template;
176         }
177         return $template;
178     }
179
180     /**
181      * Returns a KARMA object (karma votes)
182      */
183     function &getKarma($itemid) {
184         $karma =& $this->karma[$itemid];
185
186         if (!$karma) {
187             // load class if needed
188             $this->_loadClass('KARMA','KARMA.php');
189             // create KARMA object
190             $karma =& new KARMA($itemid);
191             $this->karma[$itemid] =& $karma;
192         }
193         return $karma;
194     }
195
196     /**
197      * Returns a MEMBER object
198      */
199     function &getMember($memberid) {
200         $mem =& $this->members[$memberid];
201
202         if (!$mem) {
203             // load class if needed
204             $this->_loadClass('MEMBER','MEMBER.php');
205             // create MEMBER object
206             $mem =& MEMBER::createFromID($memberid);
207             $this->members[$memberid] =& $mem;
208         }
209         return $mem;
210     }
211
212     /**
213      * Set the global parser preferences
214      */
215     function setParserProperty($name, $value) {
216         $this->parserPrefs[$name] = $value;
217     }
218
219     /**
220      * Get the global parser preferences
221      */
222     function getParserProperty($name) {
223         return $this->parserPrefs[$name];
224     }
225
226     /**
227       * A helper function to load a class
228       * 
229       * private
230       */
231     function _loadClass($name, $filename) {
232         if (!class_exists($name)) {
233                 global $DIR_LIBS;
234                 include($DIR_LIBS . $filename);
235         }
236     }
237
238     /**
239       * A helper function to load a plugin
240       * 
241       * private
242       */
243     function _loadPlugin($name) {
244         if (!class_exists($name)) {
245                 global $DIR_PLUGINS;
246
247                 $fileName = $DIR_PLUGINS . $name . '.php';
248
249                 if (!file_exists($fileName))
250                 {
251                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINFILE_NOTFOUND, $name));
252                     return 0;
253                 }
254
255                 // load plugin
256                 include($fileName);
257
258                 // check if class exists (avoid errors in eval'd code)
259                 if (!class_exists($name))
260                 {
261                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINFILE_NOCLASS, $name));
262                     return 0;
263                 }
264
265                 // add to plugin array
266                 eval('$this->plugins[$name] =& new ' . $name . '();');
267
268                 // get plugid
269                 $this->plugins[$name]->plugid = $this->getPidFromName($name);
270
271                 // unload plugin if a prefix is used and the plugin cannot handle this^
272                 global $MYSQL_PREFIX;
273                 if (($MYSQL_PREFIX != '') && !$this->plugins[$name]->supportsFeature('SqlTablePrefix'))
274                 {
275                     unset($this->plugins[$name]);
276                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINTABLEPREFIX_NOTSUPPORT, $name));
277                     return 0;
278                 }
279
280                 // unload plugin if using non-mysql handler and plugin does not support it 
281                 global $MYSQL_HANDLER;
282                 if ((!in_array('mysql',$MYSQL_HANDLER)) && !$this->plugins[$name]->supportsFeature('SqlApi'))
283                 {
284                     unset($this->plugins[$name]);
285                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINSQLAPI_NOTSUPPORT, $name));
286                     return 0;
287                 }
288
289                 // call init method
290                 $this->plugins[$name]->init();
291
292         }
293     }
294
295     /**
296      * Returns a PLUGIN object
297      */
298     function &getPlugin($name) {
299         // retrieve the name of the plugin in the right capitalisation
300         $name = $this->getUpperCaseName ($name);
301         // get the plugin   
302         $plugin =& $this->plugins[$name];
303
304         if (!$plugin) {
305             // load class if needed
306             $this->_loadPlugin($name);
307             $plugin =& $this->plugins[$name];
308         }
309         return $plugin;
310     }
311
312     /**
313       * Checks if the given plugin IS loaded or not
314       */
315     function &pluginLoaded($name) {
316         $plugin =& $this->plugins[$name];
317         return $plugin;
318     }
319
320     function &pidLoaded($pid) {
321         $plugin=false;
322         reset($this->plugins);
323         while (list($name) = each($this->plugins)) {
324             if ($pid!=$this->plugins[$name]->getId()) continue;
325             $plugin= & $this->plugins[$name];
326             break;
327         }
328         return $plugin;
329     }
330
331     /**
332       * checks if the given plugin IS installed or not
333       */
334     function pluginInstalled($name) {
335         $this->_initCacheInfo('installedPlugins');
336         return ($this->getPidFromName($name) != -1);
337     }
338
339     function pidInstalled($pid) {
340         $this->_initCacheInfo('installedPlugins');
341         return ($this->cachedInfo['installedPlugins'][$pid] != '');
342     }
343
344     function getPidFromName($name) {
345         $this->_initCacheInfo('installedPlugins');
346         foreach ($this->cachedInfo['installedPlugins'] as $pid => $pfile)
347         {
348             if (strtolower($pfile) == strtolower($name))
349                 return $pid;
350         }
351         return -1;
352     }
353
354     /**
355       * Retrieve the name of a plugin in the right capitalisation
356       */
357     function getUpperCaseName ($name) {
358         $this->_initCacheInfo('installedPlugins');
359         foreach ($this->cachedInfo['installedPlugins'] as $pid => $pfile)
360         {
361             if (strtolower($pfile) == strtolower($name))
362                 return $pfile;
363         }
364         return -1;
365     }
366
367     function clearCachedInfo($what) {
368         unset($this->cachedInfo[$what]);
369     }
370
371     /**
372      * Loads some info on the first call only
373      */
374     function _initCacheInfo($what)
375     {
376         if (isset($this->cachedInfo[$what]) && is_array($this->cachedInfo[$what]))
377             return;
378         switch ($what)
379         {
380             // 'installedPlugins' = array ($pid => $name)
381             case 'installedPlugins':
382                 $this->cachedInfo['installedPlugins'] = array();
383                 $res = sql_query('SELECT pid, pfile FROM ' . sql_table('plugin'));
384                 while ($o = sql_fetch_object($res))
385                 {
386                     $this->cachedInfo['installedPlugins'][$o->pid] = $o->pfile;
387                 }
388                 break;
389         }
390     }
391
392     /**
393       * A function to notify plugins that something has happened. Only the plugins
394       * that are subscribed to the event will get notified.
395       * Upon the first call, the list of subscriptions will be fetched from the
396       * database. The plugins itsself will only get loaded when they are first needed
397       *
398       * @param $eventName
399       *     Name of the event (method to be called on plugins)
400       * @param $data
401       *     Can contain any type of data, depending on the event type. Usually this is
402       *     an itemid, blogid, ... but it can also be an array containing multiple values
403       */
404     function notify($eventName, $data) {
405         // load subscription list if needed
406         if (!is_array($this->subscriptions))
407             $this->_loadSubscriptions();
408
409
410         // get listening objects
411         $listeners = false;
412         if (isset($this->subscriptions[$eventName])) {
413             $listeners = $this->subscriptions[$eventName];
414         }
415
416         // notify all of them
417         if (is_array($listeners)) {
418             foreach($listeners as $listener) {
419                 // load class if needed
420                 $this->_loadPlugin($listener);
421                 // do notify (if method exists)
422                 if (isset($this->plugins[$listener]) && method_exists($this->plugins[$listener], 'event_' . $eventName))
423                     call_user_func(array(&$this->plugins[$listener],'event_' . $eventName), &$data);
424             }
425         }
426
427     }
428
429     /**
430       * Loads plugin subscriptions
431       */
432     function _loadSubscriptions() {
433         // initialize as array
434         $this->subscriptions = array();
435
436         $res = sql_query('SELECT p.pfile as pfile, e.event as event FROM '.sql_table('plugin_event').' as e, '.sql_table('plugin').' as p WHERE e.pid=p.pid ORDER BY p.porder ASC');
437         while ($o = sql_fetch_object($res)) {
438             $pluginName = $o->pfile;
439             $eventName = $o->event;
440             $this->subscriptions[$eventName][] = $pluginName;
441         }
442
443     }
444
445     /*
446         Ticket functions. These are uses by the admin area to make it impossible to simulate certain GET/POST
447         requests. tickets are user specific
448     */
449
450     var $currentRequestTicket = '';
451
452     /**
453      * GET requests: Adds ticket to URL (URL should NOT be html-encoded!, ticket is added at the end)
454      */
455     function addTicketToUrl($url)
456     {
457         $ticketCode = 'ticket=' . $this->_generateTicket();
458         if (strstr($url, '?'))
459             return $url . '&' . $ticketCode;
460         else
461             return $url . '?' . $ticketCode;
462     }
463
464     /**
465      * POST requests: Adds ticket as hidden formvar
466      */
467     function addTicketHidden()
468     {
469         $ticket = $this->_generateTicket();
470
471         echo '<input type="hidden" name="ticket" value="', htmlspecialchars($ticket), '" />';
472     }
473
474     /**
475      * Get a new ticket
476      * (xmlHTTPRequest AutoSaveDraft uses this to refresh the ticket)
477      */
478     function getNewTicket()
479     {
480         $this->currentRequestTicket = '';
481         return $this->_generateTicket();
482     }
483
484     /**
485      * Checks the ticket that was passed along with the current request
486      */
487     function checkTicket()
488     {
489         global $member;
490
491         // get ticket from request
492         $ticket = requestVar('ticket');
493
494         // no ticket -> don't allow
495         if ($ticket == '')
496             return false;
497
498         // remove expired tickets first
499         $this->_cleanUpExpiredTickets();
500
501         // get member id
502         if (!$member->isLoggedIn())
503             $memberId = -1;
504         else
505             $memberId = $member->getID();
506
507         // check if ticket is a valid one
508         $query = 'SELECT COUNT(*) as result FROM ' . sql_table('tickets') . ' WHERE member=' . intval($memberId). ' and ticket=\''.addslashes($ticket).'\'';
509         if (quickQuery($query) == 1)
510         {
511             // [in the original implementation, the checked ticket was deleted. This would lead to invalid
512             //  tickets when using the browsers back button and clicking another link/form
513             //  leaving the keys in the database is not a real problem, since they're member-specific and
514             //  only valid for a period of one hour
515             // ]
516             // sql_query('DELETE FROM '.sql_table('tickets').' WHERE member=' . intval($memberId). ' and ticket=\''.addslashes($ticket).'\'');
517             return true;
518         } else {
519             // not a valid ticket
520             return false;
521         }
522
523     }
524
525     /**
526      * (internal method) Removes the expired tickets
527      */
528     function _cleanUpExpiredTickets()
529     {
530         // remove tickets older than 1 hour
531         $oldTime = time() - 60 * 60;
532         $query = 'DELETE FROM ' . sql_table('tickets'). ' WHERE ctime < \'' . date('Y-m-d H:i:s',$oldTime) .'\'';
533         sql_query($query);
534     }
535
536     /**
537      * (internal method) Generates/returns a ticket (one ticket per page request)
538      */
539     function _generateTicket()
540     {
541         if ($this->currentRequestTicket == '')
542         {
543             // generate new ticket (only one ticket will be generated per page request)
544             // and store in database
545             global $member;
546             // get member id
547             if (!$member->isLoggedIn())
548                 $memberId = -1;
549             else
550                 $memberId = $member->getID();
551
552             $ok = false;
553             while (!$ok)
554             {
555                 // generate a random token
556                 srand((double)microtime()*1000000);
557                 $ticket = md5(uniqid(rand(), true));
558
559                 // add in database as non-active
560                 $query = 'INSERT INTO ' . sql_table('tickets') . ' (ticket, member, ctime) ';
561                 $query .= 'VALUES (\'' . addslashes($ticket). '\', \'' . intval($memberId). '\', \'' . date('Y-m-d H:i:s',time()) . '\')';
562                 if (sql_query($query))
563                     $ok = true;
564             }
565
566             $this->currentRequestTicket = $ticket;
567         }
568         return $this->currentRequestTicket;
569     }
570
571 }
572
573 ?>