OSDN Git Service

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