OSDN Git Service

c898977cfdbf4f3a9a92c19f796baed1d70d3fcd
[nucleus-jp/nucleus-jp-ancient.git] / nucleus / libs / MANAGER.php
1 <?php
2 /*
3  * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4  * Copyright (C) 2002-2011 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-2011 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                     if (!defined('_MANAGER_PLUGINFILE_NOTFOUND')) {
252                         define('_MANAGER_PLUGINFILE_NOTFOUND', 'Plugin %s was not loaded (File not found)');
253                     }
254                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINFILE_NOTFOUND, $name));
255                     return 0;
256                 }
257
258                 // load plugin
259                 include($fileName);
260
261                 // check if class exists (avoid errors in eval'd code)
262                 if (!class_exists($name))
263                 {
264                     if (!defined('_MANAGER_PLUGINFILE_NOCLASS')) {
265                         define('_MANAGER_PLUGINFILE_NOCLASS', "Plugin %s was not loaded (Class not found in file, possible parse error)");
266                     }
267                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINFILE_NOCLASS, $name));
268                     return 0;
269                 }
270
271                 // add to plugin array
272                 eval('$this->plugins[$name] =& new ' . $name . '();');
273
274                 // get plugid
275                 $this->plugins[$name]->plugid = $this->getPidFromName($name);
276
277                 // unload plugin if a prefix is used and the plugin cannot handle this^
278                 global $MYSQL_PREFIX;
279                 if (($MYSQL_PREFIX != '') && !$this->plugins[$name]->supportsFeature('SqlTablePrefix'))
280                 {
281                     unset($this->plugins[$name]);
282                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINTABLEPREFIX_NOTSUPPORT, $name));
283                     return 0;
284                 }
285
286                 // unload plugin if using non-mysql handler and plugin does not support it 
287                 global $MYSQL_HANDLER;
288                 if ((!in_array('mysql',$MYSQL_HANDLER)) && !$this->plugins[$name]->supportsFeature('SqlApi'))
289                 {
290                     unset($this->plugins[$name]);
291                     ACTIONLOG::add(WARNING, sprintf(_MANAGER_PLUGINSQLAPI_NOTSUPPORT, $name));
292                     return 0;
293                 }
294
295                 // call init method
296                 $this->plugins[$name]->init();
297
298         }
299     }
300
301     /**
302      * Returns a PLUGIN object
303      */
304     function &getPlugin($name) {
305         // retrieve the name of the plugin in the right capitalisation
306         $name = $this->getUpperCaseName ($name);
307         // get the plugin   
308         $plugin =& $this->plugins[$name];
309
310         if (!$plugin) {
311             // load class if needed
312             $this->_loadPlugin($name);
313             $plugin =& $this->plugins[$name];
314         }
315         return $plugin;
316     }
317
318     /**
319       * Checks if the given plugin IS loaded or not
320       */
321     function &pluginLoaded($name) {
322         $plugin =& $this->plugins[$name];
323         return $plugin;
324     }
325
326     function &pidLoaded($pid) {
327         $plugin=false;
328         reset($this->plugins);
329         while (list($name) = each($this->plugins)) {
330             if ($pid!=$this->plugins[$name]->getId()) continue;
331             $plugin= & $this->plugins[$name];
332             break;
333         }
334         return $plugin;
335     }
336
337     /**
338       * checks if the given plugin IS installed or not
339       */
340     function pluginInstalled($name) {
341         $this->_initCacheInfo('installedPlugins');
342         return ($this->getPidFromName($name) != -1);
343     }
344
345     function pidInstalled($pid) {
346         $this->_initCacheInfo('installedPlugins');
347         return ($this->cachedInfo['installedPlugins'][$pid] != '');
348     }
349
350     function getPidFromName($name) {
351         $this->_initCacheInfo('installedPlugins');
352         foreach ($this->cachedInfo['installedPlugins'] as $pid => $pfile)
353         {
354             if (strtolower($pfile) == strtolower($name))
355                 return $pid;
356         }
357         return -1;
358     }
359
360     /**
361       * Retrieve the name of a plugin in the right capitalisation
362       */
363     function getUpperCaseName ($name) {
364         $this->_initCacheInfo('installedPlugins');
365         foreach ($this->cachedInfo['installedPlugins'] as $pid => $pfile)
366         {
367             if (strtolower($pfile) == strtolower($name))
368                 return $pfile;
369         }
370         return -1;
371     }
372
373     function clearCachedInfo($what) {
374         unset($this->cachedInfo[$what]);
375     }
376
377     /**
378      * Loads some info on the first call only
379      */
380     function _initCacheInfo($what)
381     {
382         if (isset($this->cachedInfo[$what]) && is_array($this->cachedInfo[$what]))
383             return;
384         switch ($what)
385         {
386             // 'installedPlugins' = array ($pid => $name)
387             case 'installedPlugins':
388                 $this->cachedInfo['installedPlugins'] = array();
389                 $res = sql_query('SELECT pid, pfile FROM ' . sql_table('plugin'));
390                 while ($o = sql_fetch_object($res))
391                 {
392                     $this->cachedInfo['installedPlugins'][$o->pid] = $o->pfile;
393                 }
394                 break;
395         }
396     }
397
398     /**
399       * A function to notify plugins that something has happened. Only the plugins
400       * that are subscribed to the event will get notified.
401       * Upon the first call, the list of subscriptions will be fetched from the
402       * database. The plugins itsself will only get loaded when they are first needed
403       *
404       * @param $eventName
405       *     Name of the event (method to be called on plugins)
406       * @param $data
407       *     Can contain any type of data, depending on the event type. Usually this is
408       *     an itemid, blogid, ... but it can also be an array containing multiple values
409       */
410     function notify($eventName, $data) {
411         // load subscription list if needed
412         if (!is_array($this->subscriptions))
413             $this->_loadSubscriptions();
414
415
416         // get listening objects
417         $listeners = false;
418         if (isset($this->subscriptions[$eventName])) {
419             $listeners = $this->subscriptions[$eventName];
420         }
421
422         // notify all of them
423         if (is_array($listeners)) {
424             foreach($listeners as $listener) {
425                 // load class if needed
426                 $this->_loadPlugin($listener);
427                 // do notify (if method exists)
428                 if (isset($this->plugins[$listener]) && method_exists($this->plugins[$listener], 'event_' . $eventName))
429                     call_user_func(array(&$this->plugins[$listener],'event_' . $eventName), &$data);
430             }
431         }
432
433     }
434
435     /**
436       * Loads plugin subscriptions
437       */
438     function _loadSubscriptions() {
439         // initialize as array
440         $this->subscriptions = array();
441
442         $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');
443         while ($o = sql_fetch_object($res)) {
444             $pluginName = $o->pfile;
445             $eventName = $o->event;
446             $this->subscriptions[$eventName][] = $pluginName;
447         }
448
449     }
450
451     /*
452         Ticket functions. These are uses by the admin area to make it impossible to simulate certain GET/POST
453         requests. tickets are user specific
454     */
455
456     var $currentRequestTicket = '';
457
458     /**
459      * GET requests: Adds ticket to URL (URL should NOT be html-encoded!, ticket is added at the end)
460      */
461     function addTicketToUrl($url)
462     {
463         $ticketCode = 'ticket=' . $this->_generateTicket();
464         if (strstr($url, '?'))
465             return $url . '&' . $ticketCode;
466         else
467             return $url . '?' . $ticketCode;
468     }
469
470     /**
471      * POST requests: Adds ticket as hidden formvar
472      */
473     function addTicketHidden()
474     {
475         $ticket = $this->_generateTicket();
476
477         echo '<input type="hidden" name="ticket" value="', htmlspecialchars($ticket), '" />';
478     }
479
480     /**
481      * Get a new ticket
482      * (xmlHTTPRequest AutoSaveDraft uses this to refresh the ticket)
483      */
484     function getNewTicket()
485     {
486         $this->currentRequestTicket = '';
487         return $this->_generateTicket();
488     }
489
490     /**
491      * Checks the ticket that was passed along with the current request
492      */
493     function checkTicket()
494     {
495         global $member;
496
497         // get ticket from request
498         $ticket = requestVar('ticket');
499
500         // no ticket -> don't allow
501         if ($ticket == '')
502             return false;
503
504         // remove expired tickets first
505         $this->_cleanUpExpiredTickets();
506
507         // get member id
508         if (!$member->isLoggedIn())
509             $memberId = -1;
510         else
511             $memberId = $member->getID();
512
513         // check if ticket is a valid one
514         $query = 'SELECT COUNT(*) as result FROM ' . sql_table('tickets') . ' WHERE member=' . intval($memberId). ' and ticket=\''.sql_real_escape_string($ticket).'\'';
515         if (quickQuery($query) == 1)
516         {
517             // [in the original implementation, the checked ticket was deleted. This would lead to invalid
518             //  tickets when using the browsers back button and clicking another link/form
519             //  leaving the keys in the database is not a real problem, since they're member-specific and
520             //  only valid for a period of one hour
521             // ]
522             // sql_query('DELETE FROM '.sql_table('tickets').' WHERE member=' . intval($memberId). ' and ticket=\''.addslashes($ticket).'\'');
523             return true;
524         } else {
525             // not a valid ticket
526             return false;
527         }
528
529     }
530
531     /**
532      * (internal method) Removes the expired tickets
533      */
534     function _cleanUpExpiredTickets()
535     {
536         // remove tickets older than 1 hour
537         $oldTime = time() - 60 * 60;
538         $query = 'DELETE FROM ' . sql_table('tickets'). ' WHERE ctime < \'' . date('Y-m-d H:i:s',$oldTime) .'\'';
539         sql_query($query);
540     }
541
542     /**
543      * (internal method) Generates/returns a ticket (one ticket per page request)
544      */
545     function _generateTicket()
546     {
547         if ($this->currentRequestTicket == '')
548         {
549             // generate new ticket (only one ticket will be generated per page request)
550             // and store in database
551             global $member;
552             // get member id
553             if (!$member->isLoggedIn())
554                 $memberId = -1;
555             else
556                 $memberId = $member->getID();
557
558             $ok = false;
559             while (!$ok)
560             {
561                 // generate a random token
562                 srand((double)microtime()*1000000);
563                 $ticket = md5(uniqid(rand(), true));
564
565                 // add in database as non-active
566                 $query = 'INSERT INTO ' . sql_table('tickets') . ' (ticket, member, ctime) ';
567                 $query .= 'VALUES (\'' . sql_real_escape_string($ticket). '\', \'' . intval($memberId). '\', \'' . date('Y-m-d H:i:s',time()) . '\')';
568                 if (sql_query($query))
569                     $ok = true;
570             }
571
572             $this->currentRequestTicket = $ticket;
573         }
574         return $this->currentRequestTicket;
575     }
576
577 }
578
579 ?>